Compare commits

...

38 Commits

Author SHA1 Message Date
3222247969 Release v0.14.2 2022-10-31 17:38:56 -04:00
dd6c9ce2fe Avoid crashing if multiple entries exist for same URL/source
Related to #8331. We'll need to revisit some of the get/insert logic to make sure this doesn't actually happen,
but at least it'll stop crashing for now.
2022-10-31 17:38:56 -04:00
7446b28ff1 Translations update from Hosted Weblate (#8342)
Weblate translations

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hiroshi <borlonjhayron1119@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: ID-86 <id86dev@gmail.com>
Co-authored-by: Jhayron Borlon <borlonjhayron1119@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: SHA 2048 <ajrtcuev@gmail.com>
Co-authored-by: SHAWKIK ISLAM JOHA <shawkikislam@gmail.com>
Co-authored-by: Shyntia Tan <shyntia.tan@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Uzuki Shimamura <hzy980512@126.com>
Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
Co-authored-by: jinu147 <nesqea20@gmail.com>
Co-authored-by: stevenlele <stevenlele@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/eu/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/he/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ko/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/
Translation: Tachiyomi/Tachiyomi 0.x

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hiroshi <borlonjhayron1119@gmail.com>
Co-authored-by: ID-86 <id86dev@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: SHA 2048 <ajrtcuev@gmail.com>
Co-authored-by: SHAWKIK ISLAM JOHA <shawkikislam@gmail.com>
Co-authored-by: Shyntia Tan <shyntia.tan@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Uzuki Shimamura <hzy980512@126.com>
Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
Co-authored-by: jinu147 <nesqea20@gmail.com>
Co-authored-by: stevenlele <stevenlele@outlook.com>
2022-10-31 17:38:47 -04:00
38c6702b8f Perform haptic feedback where appropriate (#8378) 2022-10-31 17:23:00 -04:00
afcf4b2988 Fix resetting filter resets browse pager (#8394)
Fix resetinf filter resets browse pager
2022-10-31 17:22:53 -04:00
ebb96a6ff4 Use selectedBackground for other list items to match with others (#8379)
* Use `selectedBackground` for other list items to match with others

* Remove unused imports
2022-10-31 17:20:31 -04:00
8b0affe9bd Set softWrap to true again for Pill text (#8391) 2022-10-31 17:20:24 -04:00
1a25cea0d6 Tweak how getChapterUrl works (#8392) 2022-10-31 13:05:27 -04:00
2ecbcdf4bd Reword "title"/"titles -> "entry"/"entries" (#8390) 2022-10-31 11:24:47 -04:00
642b392d44 Fix crash in ReaderReadingModeSettings when reverse portrait orientation is set 2022-10-30 23:10:59 -04:00
8dce7b3e9e Disable ChapterHeader & ChapterDownloadIndicator click when in selection mode (#8350)
* Disable `ChapterHeader` click when in selection mode

* Disable `ChapterDownloadIndicator` click when in selection mode

* Review changes

* Merge remote-tracking branch 'origin/master' into patch-7

* Merge remote-tracking branch 'origin/master' into patch-7

* Revert back to old implementation
2022-10-30 22:57:56 -04:00
33e90d6449 Clean up library download chapters logic
We can probably clean up the same logic in the manga controller at some point too, but that stuff's messy.
Also fixes the spacing issue that the new icon introduced.
2022-10-30 22:56:07 -04:00
50b17d5d34 Add different download options within the Library (#8267)
* feat: add download options to library

* feat: use max instead of min

* feat: remove download all option

* feat: applied requested changes + rename some functions

* feat: merge downloadAllUnreadChapters and downloadUnreadChapters into one function

* Apply suggestions from code review

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* feat: apply lint suggestions + fix code

feat: apply lint suggestions + fix code

* feat: revert onClickDownload back to onDownloadClicked

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2022-10-30 22:27:48 -04:00
7818885406 Use proper content color for filter icon in library toolbar 2022-10-30 22:19:02 -04:00
26af7ccc77 Use BOM for kotlinx.coroutines dependencies 2022-10-30 19:38:18 -04:00
5d1f79012e Fix some crashes
- Delay the initial emission of updates/sources/extensions lists instead of using a state flow. This hopefully avoids rapid initial recompositions that cause the LazyColumn key duplication crashes. (Closes #8371)
- Fix a NPE in BrowseSourcePresenter
2022-10-30 18:43:16 -04:00
cac80daa71 Set source properly when creating manga entries
Fixes #8333
2022-10-30 17:40:17 -04:00
fc184f1cfa Clean up download ahead logic
- Remove redundant chapter sorting logic when fetching next chapter(s)
- Remove redundant download queue checks (it'll handle already queued or downloaded items)
- Trigger download ahead when read >= 25% of chapter rather than 20%
- Rely on download cache when checking if next chapter is downloaded to avoid jank (fixes #8328)
2022-10-30 16:59:33 -04:00
725fcbba0e Add warning about F-Droid build support in More screen 2022-10-30 16:00:19 -04:00
bdeb209d43 Downgrade to org.jetbrains.kotlinx:kotlinx-serialization-json 1.4.0
Fixes data class casting issue seen when multiple sources have the same shadowed classes.
2022-10-30 15:29:51 -04:00
a078f1ab1b Refactor search toolbar and fix browse source (#8360) 2022-10-30 13:34:47 -04:00
86c3d8c064 Use Compose fast* functions in more places 2022-10-30 12:27:12 -04:00
156191af44 Tabs: Don't explicitly set text color in the text (#8365)
The container already provides color option for both states
2022-10-30 12:04:46 -04:00
57bba9e5ab Fix Layout Inspector's Compose tree for dev flavor (#8363) 2022-10-30 11:42:06 -04:00
dd1923fe88 Remove redundant preference composables 2022-10-30 11:37:02 -04:00
df773ee15c Refactor overflow menus into a composable 2022-10-30 11:06:41 -04:00
f5451a6881 Add ability to open random manga (#8232)
* Add ability to open random manga

* Use `getMangaForCategory` instead

* Put it in overflow menu instead of using EFAB

* Partial review changes

* Merge remote-tracking branch 'refs/remotes/origin/patch-6' into patch-6

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt

* Merge remote-tracking branch 'refs/remotes/origin/patch-6' into patch-6

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt

* Wording changes
2022-10-30 10:57:33 -04:00
fcec1581b7 Fix share menu item not appearing for entries not in library 2022-10-30 10:48:25 -04:00
11cc789e36 Center global search prompt properly in library list mode 2022-10-30 10:48:25 -04:00
16f9fb2f40 Rebase Scaffold fork (#8353)
This adds content window insets supports that will be passed to
all components used except top and bottom bar.
2022-10-30 09:59:50 -04:00
6bfaa85e84 MoreScreen: Add navbar padding (#8349) 2022-10-29 23:10:18 -04:00
8f43fb9530 Update voyager to v1.0.0-rc06 (#8346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-29 23:05:51 -04:00
04d2a3399b Restore chapter description alpha (#8345)
Restore "Darken the description colors"

Restores #3858, with new values based on current standards (0.78f rather than 0.62f)

I wanted to accomplish this without having to call a const, but that felt like a logical solution as well

Of course, if you got cleaner methods do tell, thanks
2022-10-29 22:58:18 -04:00
054bf8ec5d MangaScreen: Apply bottom content padding to large screen info column (#8347) 2022-10-29 22:57:19 -04:00
8417f5a63c Add more context to obsolete extension warning 2022-10-29 16:35:32 -04:00
26b46cace0 Few UI changes (#8299)
Co-authored-by: arkon <arkon@users.noreply.github.com>
2022-10-29 16:28:25 -04:00
0849111247 Use remember var delegates in more places 2022-10-29 16:14:49 -04:00
f9c25b350e New Pager implementation (#8323)
Minimal implementation using new Compose SnapFlingBehavior
2022-10-29 12:32:55 -04:00
111 changed files with 1492 additions and 1276 deletions

View File

@ -3,7 +3,7 @@
I acknowledge that: I acknowledge that:
- I have updated: - I have updated:
- To the latest version of the app (stable is v0.14.1) - To the latest version of the app (stable is v0.14.2)
- All extensions - All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ - I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions

View File

@ -53,7 +53,7 @@ body:
label: Tachiyomi version label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**. description: You can find your Tachiyomi version in **More → About**.
placeholder: | placeholder: |
Example: "0.14.1" Example: "0.14.2"
validations: validations:
required: true required: true
@ -98,7 +98,7 @@ body:
required: true required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/). - label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true required: true
- label: I have updated the app to version **[0.14.1](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. - label: I have updated the app to version **[0.14.2](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true required: true
- label: I have updated all installed extensions. - label: I have updated all installed extensions.
required: true required: true

View File

@ -33,7 +33,7 @@ body:
required: true required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose). - label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true required: true
- label: I have updated the app to version **[0.14.1](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. - label: I have updated the app to version **[0.14.2](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View File

@ -27,8 +27,8 @@ android {
applicationId = "eu.kanade.tachiyomi" applicationId = "eu.kanade.tachiyomi"
minSdk = AndroidConfig.minSdk minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk targetSdk = AndroidConfig.targetSdk
versionCode = 90 versionCode = 91
versionName = "0.14.1" versionName = "0.14.2"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@ -113,7 +113,6 @@ android {
"META-INF/README.md", "META-INF/README.md",
"META-INF/NOTICE", "META-INF/NOTICE",
"META-INF/*.kotlin_module", "META-INF/*.kotlin_module",
"META-INF/*.version",
)) ))
} }
@ -176,8 +175,6 @@ dependencies {
implementation(compose.accompanist.webview) implementation(compose.accompanist.webview)
implementation(compose.accompanist.swiperefresh) implementation(compose.accompanist.swiperefresh)
implementation(compose.accompanist.flowlayout) implementation(compose.accompanist.flowlayout)
implementation(compose.accompanist.pager.core)
implementation(compose.accompanist.pager.indicators)
implementation(compose.accompanist.permissions) implementation(compose.accompanist.permissions)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)
@ -190,6 +187,8 @@ dependencies {
implementation(libs.sqldelight.android.paging) implementation(libs.sqldelight.android.paging)
implementation(kotlinx.reflect) implementation(kotlinx.reflect)
implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines) implementation(kotlinx.bundles.coroutines)
// AndroidX libraries // AndroidX libraries
@ -298,6 +297,11 @@ androidComponents {
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev")) variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
} }
} }
onVariants(selector().withFlavor("default" to "standard")) {
// Only excluding in standard flavor because this breaks
// Layout Inspector's Compose tree
it.packaging.resources.excludes.add("META-INF/*.version")
}
} }
tasks { tasks {

View File

@ -34,7 +34,7 @@ import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.history.interactor.DeleteAllHistory import eu.kanade.domain.history.interactor.DeleteAllHistory
import eu.kanade.domain.history.interactor.GetHistory import eu.kanade.domain.history.interactor.GetHistory
import eu.kanade.domain.history.interactor.GetNextChapter import eu.kanade.domain.history.interactor.GetNextUnreadChapters
import eu.kanade.domain.history.interactor.RemoveHistoryById import eu.kanade.domain.history.interactor.RemoveHistoryById
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
import eu.kanade.domain.history.interactor.UpsertHistory import eu.kanade.domain.history.interactor.UpsertHistory
@ -94,7 +94,7 @@ class DomainModule : InjektModule {
addFactory { GetLibraryManga(get()) } addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) } addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetManga(get()) } addFactory { GetManga(get()) }
addFactory { GetNextChapter(get(), get(), get(), get()) } addFactory { GetNextUnreadChapters(get(), get(), get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) } addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }

View File

@ -1,51 +0,0 @@
package eu.kanade.domain.history.interactor
import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.util.chapter.getChapterSort
class GetNextChapter(
private val getChapter: GetChapter,
private val getChapterByMangaId: GetChapterByMangaId,
private val getManga: GetManga,
private val historyRepository: HistoryRepository,
) {
suspend fun await(): Chapter? {
val history = historyRepository.getLastHistory() ?: return null
return await(history.mangaId, history.chapterId)
}
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
val chapter = getChapter.await(chapterId) ?: return null
val manga = getManga.await(mangaId) ?: return null
if (!chapter.read) return chapter
val chapters = getChapterByMangaId.await(mangaId)
.sortedWith(getChapterSort(manga, sortDescending = false))
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
return when (manga.sorting) {
Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
Manga.CHAPTER_SORTING_NUMBER -> {
val chapterNumber = chapter.chapterNumber
((currChapterIndex + 1) until chapters.size)
.map { chapters[it] }
.firstOrNull {
it.chapterNumber > chapterNumber && it.chapterNumber <= chapterNumber + 1
}
}
Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
chapters.drop(currChapterIndex + 1)
.firstOrNull { it.dateUpload >= chapter.dateUpload }
}
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
}
}
}

View File

@ -0,0 +1,33 @@
package eu.kanade.domain.history.interactor
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import kotlin.math.max
class GetNextUnreadChapters(
private val getChapterByMangaId: GetChapterByMangaId,
private val getManga: GetManga,
private val historyRepository: HistoryRepository,
) {
suspend fun await(): Chapter? {
val history = historyRepository.getLastHistory() ?: return null
return await(history.mangaId, history.chapterId).firstOrNull()
}
suspend fun await(mangaId: Long): List<Chapter> {
val manga = getManga.await(mangaId) ?: return emptyList()
return getChapterByMangaId.await(mangaId)
.sortedWith(getChapterSort(manga, sortDescending = false))
.filterNot { it.read }
}
suspend fun await(mangaId: Long, fromChapterId: Long): List<Chapter> {
val unreadChapters = await(mangaId)
val currChapterIndex = unreadChapters.indexOfFirst { it.id == fromChapterId }
return unreadChapters.subList(max(0, currChapterIndex), unreadChapters.size)
}
}

View File

@ -7,11 +7,11 @@ class NetworkToLocalManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
) { ) {
suspend fun await(manga: Manga, sourceId: Long): Manga { suspend fun await(manga: Manga): Manga {
val localManga = getManga(manga.url, sourceId) val localManga = getManga(manga.url, manga.source)
return when { return when {
localManga == null -> { localManga == null -> {
val id = insertManga(manga.copy(source = sourceId)) val id = insertManga(manga)
manga.copy(id = id!!) manga.copy(id = id!!)
} }
!localManga.favorite -> { !localManga.favorite -> {

View File

@ -232,7 +232,7 @@ fun Manga.toMangaUpdate(): MangaUpdate {
) )
} }
fun SManga.toDomainManga(): Manga { fun SManga.toDomainManga(sourceId: Long): Manga {
return Manga.create().copy( return Manga.create().copy(
url = url, url = url,
title = title, title = title,
@ -244,6 +244,7 @@ fun SManga.toDomainManga(): Manga {
thumbnailUrl = thumbnail_url, thumbnailUrl = thumbnail_url,
updateStrategy = update_strategy, updateStrategy = update_strategy,
initialized = initialized, initialized = initialized,
source = sourceId,
) )
} }

View File

@ -4,12 +4,9 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -55,9 +52,11 @@ import eu.kanade.presentation.components.DIVIDER_ALPHA
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
@ -195,23 +194,6 @@ private fun ExtensionDetails(
} }
} }
@Composable
private fun WarningBanner(@StringRes textRes: Int) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.error)
.padding(16.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResource(textRes),
color = MaterialTheme.colorScheme.onError,
style = MaterialTheme.typography.bodyMedium,
)
}
}
@Composable @Composable
private fun DetailsHeader( private fun DetailsHeader(
extension: Extension, extension: Extension,
@ -380,15 +362,14 @@ private fun SourceSwitchPreference(
) { ) {
val context = LocalContext.current val context = LocalContext.current
PreferenceRow( TextPreferenceWidget(
modifier = modifier, modifier = modifier,
title = if (source.labelAsName) { title = if (source.labelAsName) {
source.source.toString() source.source.toString()
} else { } else {
LocaleHelper.getSourceDisplayName(source.source.lang, context) LocaleHelper.getSourceDisplayName(source.source.lang, context)
}, },
onClick = { onClickSource(source.source.id) }, widget = {
action = {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -402,9 +383,14 @@ private fun SourceSwitchPreference(
} }
} }
Switch(checked = source.enabled, onCheckedChange = null) Switch(
checked = source.enabled,
onCheckedChange = null,
modifier = Modifier.padding(start = TrailingWidgetBuffer),
)
} }
}, },
onPreferenceClick = { onClickSource(source.source.id) },
) )
} }

View File

@ -3,7 +3,6 @@ package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -11,10 +10,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@ -42,15 +41,13 @@ fun ExtensionFilterScreen(
textResource = R.string.empty_screen, textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding), modifier = Modifier.padding(contentPadding),
) )
else -> { else -> ExtensionFilterContent(
SourceFilterContent( contentPadding = contentPadding,
contentPadding = contentPadding, state = presenter,
state = presenter, onClickLang = {
onClickLang = { presenter.toggleLanguage(it)
presenter.toggleLanguage(it) },
}, )
)
}
} }
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -65,40 +62,24 @@ fun ExtensionFilterScreen(
} }
@Composable @Composable
private fun SourceFilterContent( private fun ExtensionFilterContent(
contentPadding: PaddingValues, contentPadding: PaddingValues,
state: ExtensionFilterState, state: ExtensionFilterState,
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
) { ) {
LazyColumn( FastScrollLazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
items( items(
items = state.items, items = state.items,
) { model -> ) { model ->
ExtensionFilterItem( val lang = model.lang
SwitchPreferenceWidget(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
lang = model.lang, title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
enabled = model.enabled, checked = model.enabled,
onClickItem = onClickLang, onCheckedChanged = { onClickLang(lang) },
) )
} }
} }
} }
@Composable
private fun ExtensionFilterItem(
modifier: Modifier,
lang: String,
enabled: Boolean,
onClickItem: (String) -> Unit,
) {
PreferenceRow(
modifier = modifier,
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
action = {
Switch(checked = enabled, onCheckedChange = null)
},
onClick = { onClickItem(lang) },
)
}

View File

@ -10,9 +10,9 @@ import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.manga.components.BaseMangaListItem import eu.kanade.presentation.manga.components.BaseMangaListItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
@ -72,7 +72,7 @@ private fun MigrateMangaContent(
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit, onClickCover: (Manga) -> Unit,
) { ) {
ScrollbarLazyColumn( FastScrollLazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
items(state.items) { manga -> items(state.items) { manga ->

View File

@ -6,12 +6,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.components.BrowseSourceSearchToolbar
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.more.MoreController
@ -38,13 +36,11 @@ fun SourceSearchScreen(
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
BrowseSourceSearchToolbar( SearchToolbar(
searchQuery = presenter.searchQuery ?: "", searchQuery = presenter.searchQuery ?: "",
onSearchQueryChanged = { presenter.searchQuery = it }, onChangeSearchQuery = { presenter.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint), onClickCloseSearch = navigateUp,
navigateUp = navigateUp, onSearch = { presenter.search(it) },
onResetClick = { presenter.searchQuery = "" },
onSearchClick = { presenter.search(it) },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
}, },

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -14,10 +13,10 @@ import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter
@ -76,7 +75,7 @@ private fun SourcesFilterContent(
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
onClickSource: (Source) -> Unit, onClickSource: (Source) -> Unit,
) { ) {
ScrollbarLazyColumn( FastScrollLazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
items( items(
@ -95,14 +94,12 @@ private fun SourcesFilterContent(
}, },
) { model -> ) { model ->
when (model) { when (model) {
is FilterUiModel.Header -> { is FilterUiModel.Header -> SourcesFilterHeader(
SourcesFilterHeader( modifier = Modifier.animateItemPlacement(),
modifier = Modifier.animateItemPlacement(), language = model.language,
language = model.language, enabled = model.enabled,
enabled = model.enabled, onClickItem = onClickLang,
onClickItem = onClickLang, )
)
}
is FilterUiModel.Item -> SourcesFilterItem( is FilterUiModel.Item -> SourcesFilterItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
source = model.source, source = model.source,
@ -121,13 +118,11 @@ private fun SourcesFilterHeader(
enabled: Boolean, enabled: Boolean,
onClickItem: (String) -> Unit, onClickItem: (String) -> Unit,
) { ) {
PreferenceRow( SwitchPreferenceWidget(
modifier = modifier, modifier = modifier,
title = LocaleHelper.getSourceDisplayName(language, LocalContext.current), title = LocaleHelper.getSourceDisplayName(language, LocalContext.current),
action = { checked = enabled,
Switch(checked = enabled, onCheckedChange = null) onCheckedChanged = { onClickItem(language) },
},
onClick = { onClickItem(language) },
) )
} }

View File

@ -1,13 +1,10 @@
package eu.kanade.presentation.browse.components package eu.kanade.presentation.browse.components
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ViewList import androidx.compose.material.icons.filled.ViewList
import androidx.compose.material.icons.filled.ViewModule import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -15,14 +12,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.presentation.browse.BrowseSourceState import eu.kanade.presentation.browse.BrowseSourceState
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.RadioMenuItem import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
@ -42,59 +37,21 @@ fun BrowseSourceToolbar(
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
if (state.searchQuery == null) { // Avoid capturing unstable source in actions lambda
BrowseSourceRegularToolbar( val title = source?.name
title = if (state.isUserQuery) state.currentFilter.query else source?.name.orEmpty(), val isLocalSource = source is LocalSource
isLocalSource = source is LocalSource,
displayMode = displayMode,
onDisplayModeChange = onDisplayModeChange,
navigateUp = navigateUp,
onSearchClick = { state.searchQuery = if (state.isUserQuery) state.currentFilter.query else "" },
onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick,
scrollBehavior = scrollBehavior,
)
} else {
val cancelSearch = { state.searchQuery = null }
BrowseSourceSearchToolbar(
searchQuery = state.searchQuery!!,
onSearchQueryChanged = { state.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint),
navigateUp = cancelSearch,
onResetClick = { state.searchQuery = "" },
onSearchClick = {
onSearch(it)
cancelSearch()
},
scrollBehavior = scrollBehavior,
)
}
}
@Composable SearchToolbar(
fun BrowseSourceRegularToolbar(
title: String,
isLocalSource: Boolean,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,
onSearchClick: () -> Unit,
onWebViewClick: () -> Unit,
onHelpClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) {
AppBar(
navigateUp = navigateUp, navigateUp = navigateUp,
title = title, titleContent = { AppBarTitle(title) },
searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
onSearch = onSearch,
onClickCloseSearch = navigateUp,
actions = { actions = {
var selectingDisplayMode by remember { mutableStateOf(false) } var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions( AppBarActions(
actions = listOf( actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_search),
icon = Icons.Outlined.Search,
onClick = onSearchClick,
),
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_display_mode), title = stringResource(R.string.action_display_mode),
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule, icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
@ -123,18 +80,21 @@ fun BrowseSourceRegularToolbar(
text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) }, text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) },
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid, isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
) { ) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid) onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
} }
RadioMenuItem( RadioMenuItem(
text = { Text(text = stringResource(R.string.action_display_grid)) }, text = { Text(text = stringResource(R.string.action_display_grid)) },
isChecked = displayMode == LibraryDisplayMode.CompactGrid, isChecked = displayMode == LibraryDisplayMode.CompactGrid,
) { ) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.CompactGrid) onDisplayModeChange(LibraryDisplayMode.CompactGrid)
} }
RadioMenuItem( RadioMenuItem(
text = { Text(text = stringResource(R.string.action_display_list)) }, text = { Text(text = stringResource(R.string.action_display_list)) },
isChecked = displayMode == LibraryDisplayMode.List, isChecked = displayMode == LibraryDisplayMode.List,
) { ) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.List) onDisplayModeChange(LibraryDisplayMode.List)
} }
} }
@ -142,34 +102,3 @@ fun BrowseSourceRegularToolbar(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
} }
@Composable
fun BrowseSourceSearchToolbar(
searchQuery: String,
onSearchQueryChanged: (String) -> Unit,
placeholderText: String?,
navigateUp: () -> Unit,
onResetClick: () -> Unit,
onSearchClick: (String) -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
SearchToolbar(
searchQuery = searchQuery,
onChangeSearchQuery = onSearchQueryChanged,
placeholderText = placeholderText,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
onSearchClick(searchQuery)
focusManager.clearFocus()
keyboardController?.hide()
},
),
onClickCloseSearch = navigateUp,
onClickResetSearch = onResetClick,
scrollBehavior = scrollBehavior,
)
}

View File

@ -6,8 +6,10 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@ -22,7 +24,7 @@ fun CategoryCreateDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onCreate: (String) -> Unit, onCreate: (String) -> Unit,
) { ) {
val (name, onNameChange) = remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
AlertDialog( AlertDialog(
@ -48,7 +50,7 @@ fun CategoryCreateDialog(
modifier = Modifier modifier = Modifier
.focusRequester(focusRequester), .focusRequester(focusRequester),
value = name, value = name,
onValueChange = onNameChange, onValueChange = { name = it },
label = { label = {
Text(text = stringResource(R.string.name)) Text(text = stringResource(R.string.name))
}, },
@ -70,7 +72,7 @@ fun CategoryRenameDialog(
onRename: (String) -> Unit, onRename: (String) -> Unit,
category: Category, category: Category,
) { ) {
val (name, onNameChange) = remember { mutableStateOf(category.name) } var name by remember { mutableStateOf(category.name) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
AlertDialog( AlertDialog(
@ -96,7 +98,7 @@ fun CategoryRenameDialog(
modifier = Modifier modifier = Modifier
.focusRequester(focusRequester), .focusRequester(focusRequester),
value = name, value = name,
onValueChange = onNameChange, onValueChange = { name = it },
label = { label = {
Text(text = stringResource(R.string.name)) Text(text = stringResource(R.string.name))
}, },

View File

@ -1,13 +1,11 @@
package eu.kanade.presentation.category.components package eu.kanade.presentation.category.components
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.util.isScrolledToEnd import eu.kanade.presentation.util.isScrolledToEnd
@ -23,8 +21,6 @@ fun CategoryFloatingActionButton(
text = { Text(text = stringResource(R.string.action_add)) }, text = { Text(text = stringResource(R.string.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
onClick = onCreate, onClick = onCreate,
modifier = Modifier
.navigationBarsPadding(),
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(), expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
) )
} }

View File

@ -1,12 +1,9 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -15,6 +12,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -28,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -36,8 +35,11 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -140,7 +142,6 @@ fun AppBar(
}, },
title = titleContent, title = titleContent,
actions = actions, actions = actions,
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
elevation = if (isActionMode) 3.dp else 0.dp, elevation = if (isActionMode) 3.dp else 0.dp,
@ -218,15 +219,22 @@ fun AppBarActions(
} }
} }
/**
* @param searchEnabled Set to false if you don't want to show search action.
* @param searchQuery If null, use normal toolbar.
* @param placeholderText If null, [R.string.action_search_hint] is used.
*/
@Composable @Composable
fun SearchToolbar( fun SearchToolbar(
searchQuery: String, titleContent: @Composable () -> Unit = {},
onChangeSearchQuery: (String) -> Unit, navigateUp: (() -> Unit)? = null,
searchEnabled: Boolean = true,
searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
placeholderText: String? = null, placeholderText: String? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, onSearch: (String) -> Unit = {},
keyboardActions: KeyboardActions = KeyboardActions.Default, onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
onClickCloseSearch: () -> Unit, actions: @Composable RowScope.() -> Unit = {},
onClickResetSearch: () -> Unit,
incognitoMode: Boolean = false, incognitoMode: Boolean = false,
downloadedOnlyMode: Boolean = false, downloadedOnlyMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
@ -234,9 +242,15 @@ fun SearchToolbar(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
var searchClickCount by remember { mutableStateOf(0) }
AppBar( AppBar(
titleContent = { titleContent = {
if (searchQuery == null) return@AppBar titleContent()
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
BasicTextField( BasicTextField(
value = searchQuery, value = searchQuery,
onValueChange = onChangeSearchQuery, onValueChange = onChangeSearchQuery,
@ -248,8 +262,14 @@ fun SearchToolbar(
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 18.sp, fontSize = 18.sp,
), ),
keyboardOptions = keyboardOptions, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = keyboardActions, keyboardActions = KeyboardActions(
onSearch = {
onSearch(searchQuery)
focusManager.clearFocus()
keyboardController?.hide()
},
),
singleLine = true, singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
@ -263,7 +283,7 @@ fun SearchToolbar(
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
interactionSource = interactionSource, interactionSource = interactionSource,
placeholder = { placeholder = {
if (!placeholderText.isNullOrEmpty()) { (placeholderText ?: stringResource(R.string.action_search_hint)).let { placeholderText ->
Text( Text(
modifier = Modifier.secondaryItemAlpha(), modifier = Modifier.secondaryItemAlpha(),
text = placeholderText, text = placeholderText,
@ -280,22 +300,41 @@ fun SearchToolbar(
}, },
) )
}, },
navigationIcon = Icons.Outlined.ArrowBack, navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch,
navigateUp = onClickCloseSearch,
actions = { actions = {
AnimatedVisibility(visible = searchQuery.isNotEmpty()) { key("search") {
IconButton(onClick = onClickResetSearch) { val onClick = {
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset)) searchClickCount++
onChangeSearchQuery("")
}
if (!searchEnabled) {
// Don't show search action
} else if (searchQuery == null) {
IconButton(onClick) {
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
}
} else if (searchQuery.isNotEmpty()) {
IconButton(onClick) {
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
}
} }
} }
key("actions") { actions() }
}, },
isActionMode = false, isActionMode = false,
downloadedOnlyMode = downloadedOnlyMode, downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode, incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
LaunchedEffect(focusRequester) { LaunchedEffect(searchClickCount) {
focusRequester.requestFocus() if (searchQuery == null) return@LaunchedEffect
try {
focusRequester.requestFocus()
} catch (_: Throwable) {
// TextField is gone
}
} }
} }

View File

@ -1,5 +1,6 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.annotation.StringRes
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -13,6 +14,23 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@Composable
fun WarningBanner(
@StringRes textRes: Int,
modifier: Modifier = Modifier,
) {
Text(
text = stringResource(textRes),
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.error)
.padding(16.dp),
color = MaterialTheme.colorScheme.onError,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
)
}
@Composable @Composable
fun AppStateBanners( fun AppStateBanners(
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,

View File

@ -26,6 +26,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
@ -43,26 +45,41 @@ enum class ChapterDownloadAction {
@Composable @Composable
fun ChapterDownloadIndicator( fun ChapterDownloadIndicator(
enabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
downloadStateProvider: () -> Download.State, downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
onClick: (ChapterDownloadAction) -> Unit, onClick: (ChapterDownloadAction) -> Unit,
) { ) {
when (val downloadState = downloadStateProvider()) { when (val downloadState = downloadStateProvider()) {
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(modifier = modifier, onClick = onClick) Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(
enabled = enabled,
modifier = modifier,
onClick = onClick,
)
Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator( Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator(
enabled = enabled,
modifier = modifier, modifier = modifier,
downloadState = downloadState, downloadState = downloadState,
downloadProgressProvider = downloadProgressProvider, downloadProgressProvider = downloadProgressProvider,
onClick = onClick, onClick = onClick,
) )
Download.State.DOWNLOADED -> DownloadedIndicator(modifier = modifier, onClick = onClick) Download.State.DOWNLOADED -> DownloadedIndicator(
Download.State.ERROR -> ErrorIndicator(modifier = modifier, onClick = onClick) enabled = enabled,
modifier = modifier,
onClick = onClick,
)
Download.State.ERROR -> ErrorIndicator(
enabled = enabled,
modifier = modifier,
onClick = onClick,
)
} }
} }
@Composable @Composable
private fun NotDownloadedIndicator( private fun NotDownloadedIndicator(
enabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit, onClick: (ChapterDownloadAction) -> Unit,
) { ) {
@ -70,6 +87,7 @@ private fun NotDownloadedIndicator(
modifier = modifier modifier = modifier
.size(IconButtonTokens.StateLayerSize) .size(IconButtonTokens.StateLayerSize)
.commonClickable( .commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.START_NOW) }, onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
onClick = { onClick(ChapterDownloadAction.START) }, onClick = { onClick(ChapterDownloadAction.START) },
) )
@ -87,6 +105,7 @@ private fun NotDownloadedIndicator(
@Composable @Composable
private fun DownloadingIndicator( private fun DownloadingIndicator(
enabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
downloadState: Download.State, downloadState: Download.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
@ -97,6 +116,7 @@ private fun DownloadingIndicator(
modifier = modifier modifier = modifier
.size(IconButtonTokens.StateLayerSize) .size(IconButtonTokens.StateLayerSize)
.commonClickable( .commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.CANCEL) }, onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
onClick = { isMenuExpanded = true }, onClick = { isMenuExpanded = true },
), ),
@ -158,6 +178,7 @@ private fun DownloadingIndicator(
@Composable @Composable
private fun DownloadedIndicator( private fun DownloadedIndicator(
enabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit, onClick: (ChapterDownloadAction) -> Unit,
) { ) {
@ -166,6 +187,7 @@ private fun DownloadedIndicator(
modifier = modifier modifier = modifier
.size(IconButtonTokens.StateLayerSize) .size(IconButtonTokens.StateLayerSize)
.commonClickable( .commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.DELETE) }, onLongClick = { onClick(ChapterDownloadAction.DELETE) },
onClick = { isMenuExpanded = true }, onClick = { isMenuExpanded = true },
), ),
@ -191,6 +213,7 @@ private fun DownloadedIndicator(
@Composable @Composable
private fun ErrorIndicator( private fun ErrorIndicator(
enabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit, onClick: (ChapterDownloadAction) -> Unit,
) { ) {
@ -198,6 +221,7 @@ private fun ErrorIndicator(
modifier = modifier modifier = modifier
.size(IconButtonTokens.StateLayerSize) .size(IconButtonTokens.StateLayerSize)
.commonClickable( .commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.START) }, onLongClick = { onClick(ChapterDownloadAction.START) },
onClick = { onClick(ChapterDownloadAction.START) }, onClick = { onClick(ChapterDownloadAction.START) },
), ),
@ -213,11 +237,18 @@ private fun ErrorIndicator(
} }
private fun Modifier.commonClickable( private fun Modifier.commonClickable(
enabled: Boolean,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
) = composed { ) = composed {
val haptic = LocalHapticFeedback.current
this.combinedClickable( this.combinedClickable(
onLongClick = onLongClick, enabled = enabled,
onLongClick = {
onLongClick()
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onClick = onClick, onClick = onClick,
role = Role.Button, role = Role.Button,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },

View File

@ -0,0 +1,17 @@
package eu.kanade.presentation.components
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
const val DIVIDER_ALPHA = 0.2f
@Composable
fun Divider(
modifier: Modifier = Modifier,
) {
androidx.compose.material3.Divider(
modifier = modifier,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
)
}

View File

@ -0,0 +1,66 @@
package eu.kanade.presentation.components
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R
@Composable
fun DownloadDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
includeDownloadAllOption: Boolean = true,
) {
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_1)) },
onClick = {
onDownloadClicked(DownloadAction.NEXT_1_CHAPTER)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_5)) },
onClick = {
onDownloadClicked(DownloadAction.NEXT_5_CHAPTERS)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_10)) },
onClick = {
onDownloadClicked(DownloadAction.NEXT_10_CHAPTERS)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_custom)) },
onClick = {
onDownloadClicked(DownloadAction.CUSTOM)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_unread)) },
onClick = {
onDownloadClicked(DownloadAction.UNREAD_CHAPTERS)
onDismissRequest()
},
)
if (includeDownloadAllOption) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_all)) },
onClick = {
onDownloadClicked(DownloadAction.ALL_CHAPTERS)
onDismissRequest()
},
)
}
}
}

View File

@ -1,20 +1,29 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.RadioButtonChecked import androidx.compose.material.icons.outlined.RadioButtonChecked
import androidx.compose.material.icons.outlined.RadioButtonUnchecked import androidx.compose.material.icons.outlined.RadioButtonUnchecked
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import me.saket.cascade.CascadeColumnScope
import me.saket.cascade.CascadeDropdownMenu
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
@Composable @Composable
@ -22,6 +31,7 @@ fun DropdownMenu(
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(8.dp, (-56).dp),
properties: PopupProperties = PopupProperties(focusable = true), properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
@ -29,7 +39,7 @@ fun DropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp), modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
offset = DpOffset(8.dp, (-56).dp), offset = offset,
properties = properties, properties = properties,
content = content, content = content,
) )
@ -60,3 +70,27 @@ fun RadioMenuItem(
}, },
) )
} }
@Composable
fun OverflowMenu(
content: @Composable CascadeColumnScope.(() -> Unit) -> Unit,
) {
var moreExpanded by remember { mutableStateOf(false) }
val closeMenu = { moreExpanded = false }
Box {
IconButton(onClick = { moreExpanded = !moreExpanded }) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
)
}
CascadeDropdownMenu(
expanded = moreExpanded,
onDismissRequest = closeMenu,
offset = DpOffset(8.dp, (-56).dp),
) {
content(closeMenu)
}
}
}

View File

@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.util.secondaryItemAlpha import eu.kanade.presentation.util.secondaryItemAlpha
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -100,9 +101,9 @@ fun EmptyScreen(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
) { measurables, constraints -> ) { measurables, constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val facePlaceable = measurables.first { it.layoutId == "face" } val facePlaceable = measurables.fastFirstOrNull { it.layoutId == "face" }!!
.measure(looseConstraints) .measure(looseConstraints)
val actionsPlaceable = measurables.firstOrNull { it.layoutId == "actions" } val actionsPlaceable = measurables.fastFirstOrNull { it.layoutId == "actions" }
?.measure(looseConstraints) ?.measure(looseConstraints)
layout(constraints.maxWidth, constraints.maxHeight) { layout(constraints.maxWidth, constraints.maxHeight) {

View File

@ -37,8 +37,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -48,6 +50,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -170,6 +173,7 @@ private fun RowScope.Button(
toConfirm: Boolean, toConfirm: Boolean,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
content: (@Composable () -> Unit)? = null,
) { ) {
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f) val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
Column( Column(
@ -201,6 +205,7 @@ private fun RowScope.Button(
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
) )
} }
content?.invoke()
} }
} }
@ -211,7 +216,7 @@ fun LibraryBottomActionMenu(
onChangeCategoryClicked: (() -> Unit)?, onChangeCategoryClicked: (() -> Unit)?,
onMarkAsReadClicked: (() -> Unit)?, onMarkAsReadClicked: (() -> Unit)?,
onMarkAsUnreadClicked: (() -> Unit)?, onMarkAsUnreadClicked: (() -> Unit)?,
onDownloadClicked: (() -> Unit)?, onDownloadClicked: ((DownloadAction) -> Unit)?,
onDeleteClicked: (() -> Unit)?, onDeleteClicked: (() -> Unit)?,
) { ) {
AnimatedVisibility( AnimatedVisibility(
@ -270,13 +275,22 @@ fun LibraryBottomActionMenu(
) )
} }
if (onDownloadClicked != null) { if (onDownloadClicked != null) {
var downloadExpanded by remember { mutableStateOf(false) }
Button( Button(
title = stringResource(R.string.action_download), title = stringResource(R.string.action_download),
icon = Icons.Outlined.Download, icon = Icons.Outlined.Download,
toConfirm = confirm[3], toConfirm = confirm[3],
onLongClick = { onLongClickItem(3) }, onLongClick = { onLongClickItem(3) },
onClick = onDownloadClicked, onClick = { downloadExpanded = !downloadExpanded },
) ) {
val onDismissRequest = { downloadExpanded = false }
DownloadDropdownMenu(
expanded = downloadExpanded,
onDismissRequest = onDismissRequest,
onDownloadClicked = onDownloadClicked,
includeDownloadAllOption = false,
)
}
} }
if (onDeleteClicked != null) { if (onDeleteClicked != null) {
Button( Button(

View File

@ -0,0 +1,182 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastMaxBy
import kotlinx.coroutines.flow.distinctUntilChanged
@Composable
fun HorizontalPager(
count: Int,
modifier: Modifier = Modifier,
state: PagerState = rememberPagerState(),
key: ((page: Int) -> Any)? = null,
contentPadding: PaddingValues = PaddingValues(),
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
userScrollEnabled: Boolean = true,
content: @Composable BoxScope.(page: Int) -> Unit,
) {
Pager(
count = count,
modifier = modifier,
state = state,
isVertical = false,
key = key,
contentPadding = contentPadding,
verticalAlignment = verticalAlignment,
userScrollEnabled = userScrollEnabled,
content = content,
)
}
@Composable
private fun Pager(
count: Int,
modifier: Modifier,
state: PagerState,
isVertical: Boolean,
key: ((page: Int) -> Any)?,
contentPadding: PaddingValues,
userScrollEnabled: Boolean,
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
content: @Composable BoxScope.(page: Int) -> Unit,
) {
LaunchedEffect(count) {
state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
}
LaunchedEffect(state) {
snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
.distinctUntilChanged()
.collect { state.updateCurrentPageBasedOnLazyListState() }
}
if (isVertical) {
LazyColumn(
modifier = modifier,
state = state.lazyListState,
contentPadding = contentPadding,
horizontalAlignment = horizontalAlignment,
verticalArrangement = Arrangement.aligned(verticalAlignment),
userScrollEnabled = userScrollEnabled,
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
) {
items(
count = count,
key = key,
) { page ->
Box(
modifier = Modifier
.fillParentMaxHeight()
.wrapContentSize(),
) {
content(this, page)
}
}
}
} else {
LazyRow(
modifier = modifier,
state = state.lazyListState,
contentPadding = contentPadding,
verticalAlignment = verticalAlignment,
horizontalArrangement = Arrangement.aligned(horizontalAlignment),
userScrollEnabled = userScrollEnabled,
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
) {
items(
count = count,
key = key,
) { page ->
Box(
modifier = Modifier
.fillParentMaxWidth()
.wrapContentSize(),
) {
content(this, page)
}
}
}
}
}
@Composable
fun rememberPagerState(
initialPage: Int = 0,
) = rememberSaveable(saver = PagerState.Saver) {
PagerState(currentPage = initialPage)
}
@Stable
class PagerState(
currentPage: Int = 0,
) {
init { check(currentPage >= 0) { "currentPage cannot be less than zero" } }
val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
private var _currentPage by mutableStateOf(currentPage)
var currentPage: Int
get() = _currentPage
set(value) {
if (value != _currentPage) {
_currentPage = value
}
}
val mostVisiblePageLayoutInfo: LazyListItemInfo?
get() {
val layoutInfo = lazyListState.layoutInfo
return layoutInfo.visibleItemsInfo.fastMaxBy {
val start = maxOf(it.offset, 0)
val end = minOf(
it.offset + it.size,
layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding,
)
end - start
}
}
fun updateCurrentPageBasedOnLazyListState() {
mostVisiblePageLayoutInfo?.let {
currentPage = it.index
}
}
suspend fun animateScrollToPage(page: Int) {
lazyListState.animateScrollToItem(index = page)
}
suspend fun scrollToPage(page: Int) {
lazyListState.scrollToItem(index = page)
updateCurrentPageBasedOnLazyListState()
}
companion object {
val Saver: Saver<PagerState, *> = listSaver(
save = { listOf(it.currentPage) },
restore = { PagerState(it[0]) },
)
}
}

View File

@ -42,7 +42,6 @@ fun Pill(
text = text, text = text,
fontSize = fontSize, fontSize = fontSize,
maxLines = 1, maxLines = 1,
softWrap = false,
) )
} }
} }

View File

@ -1,167 +0,0 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.util.horizontalPadding
const val DIVIDER_ALPHA = 0.2f
@Composable
fun Divider(
modifier: Modifier = Modifier,
) {
androidx.compose.material3.Divider(
modifier = modifier,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
)
}
@Composable
fun PreferenceRow(
modifier: Modifier = Modifier,
title: String,
painter: Painter? = null,
onClick: () -> Unit = {},
onLongClick: () -> Unit = {},
subtitle: String? = null,
action: @Composable (() -> Unit)? = null,
) {
val height = if (subtitle != null) 72.dp else 56.dp
val titleTextStyle = MaterialTheme.typography.bodyLarge
val subtitleTextStyle = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f),
)
Row(
modifier = modifier
.fillMaxWidth()
.heightIn(min = height)
.combinedClickable(
onLongClick = onLongClick,
onClick = onClick,
),
verticalAlignment = Alignment.CenterVertically,
) {
if (painter != null) {
Icon(
painter = painter,
modifier = Modifier
.padding(start = horizontalPadding, end = 16.dp)
.size(24.dp),
tint = MaterialTheme.colorScheme.primary,
contentDescription = null,
)
}
Column(
Modifier
.padding(horizontal = 16.dp)
.weight(1f),
) {
Text(
text = title,
style = titleTextStyle,
)
if (subtitle != null) {
Text(
modifier = Modifier.padding(top = 4.dp),
text = subtitle,
style = subtitleTextStyle,
)
}
}
if (action != null) {
Box(
Modifier
.widthIn(min = 56.dp)
.padding(end = horizontalPadding),
) {
action()
}
}
}
}
@Composable
fun SwitchPreference(
modifier: Modifier = Modifier,
checked: Boolean,
onClick: () -> Unit,
title: String,
subtitle: String? = null,
painter: Painter? = null,
) {
PreferenceRow(
modifier = modifier,
title = title,
subtitle = subtitle,
painter = painter,
action = { Switch(checked = checked, onCheckedChange = null) },
onClick = onClick,
)
}
@Composable
fun SwitchPreference(
modifier: Modifier = Modifier,
preference: PreferenceMutableState<Boolean>,
title: String,
subtitle: String? = null,
painter: Painter? = null,
) {
SwitchPreference(
modifier = modifier,
title = title,
subtitle = subtitle,
painter = painter,
checked = preference.value,
onClick = { preference.value = !preference.value },
)
}
@Preview
@Composable
private fun PreferencesPreview() {
TachiyomiTheme {
Column {
PreferenceRow(
title = "Plain",
subtitle = "Subtitle",
)
Divider()
SwitchPreference(
title = "Switch (on)",
subtitle = "Subtitle",
checked = true,
onClick = {},
)
SwitchPreference(
title = "Switch (off)",
subtitle = "Subtitle",
checked = false,
onClick = {},
)
}
}
}

View File

@ -19,9 +19,11 @@ package eu.kanade.presentation.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
@ -37,6 +39,11 @@ import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy
import kotlin.math.max
/** /**
* <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>. * <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>.
@ -59,6 +66,7 @@ import androidx.compose.ui.unit.dp
* * Pass scroll behavior to top bar by default * * Pass scroll behavior to top bar by default
* * Remove height constraint for expanded app bar * * Remove height constraint for expanded app bar
* * Also take account of fab height when providing inner padding * * Also take account of fab height when providing inner padding
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
* *
* @param modifier the [Modifier] to be applied to this scaffold * @param modifier the [Modifier] to be applied to this scaffold
* @param topBar top app bar of the screen, typically a [SmallTopAppBar] * @param topBar top app bar of the screen, typically a [SmallTopAppBar]
@ -72,6 +80,9 @@ import androidx.compose.ui.unit.dp
* @param contentColor the preferred color for content inside this scaffold. Defaults to either the * @param contentColor the preferred color for content inside this scaffold. Defaults to either the
* matching content color for [containerColor], or to the current [LocalContentColor] if * matching content color for [containerColor], or to the current [LocalContentColor] if
* [containerColor] is not a color from the theme. * [containerColor] is not a color from the theme.
* @param contentWindowInsets window insets to be passed to content slot via PaddingValues params.
* Scaffold will take the insets into account from the top/bottom only if the topBar/ bottomBar
* are not present, as the scaffold expect topBar/bottomBar to handle insets instead
* @param content content of the screen. The lambda receives a [PaddingValues] that should be * @param content content of the screen. The lambda receives a [PaddingValues] that should be
* applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to * applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to
* properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to * properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to
@ -89,6 +100,7 @@ fun Scaffold(
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background, containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor), contentColor: Color = contentColorFor(containerColor),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
) { ) {
androidx.compose.material3.Surface( androidx.compose.material3.Surface(
@ -104,6 +116,7 @@ fun Scaffold(
bottomBar = bottomBar, bottomBar = bottomBar,
content = content, content = content,
snackbar = snackbarHost, snackbar = snackbarHost,
contentWindowInsets = contentWindowInsets,
fab = floatingActionButton, fab = floatingActionButton,
) )
} }
@ -129,6 +142,7 @@ private fun ScaffoldLayout(
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
snackbar: @Composable () -> Unit, snackbar: @Composable () -> Unit,
fab: @Composable () -> Unit, fab: @Composable () -> Unit,
contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit, bottomBar: @Composable () -> Unit,
) { ) {
SubcomposeLayout { constraints -> SubcomposeLayout { constraints ->
@ -143,37 +157,51 @@ private fun ScaffoldLayout(
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity) val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
layout(layoutWidth, layoutHeight) { layout(layoutWidth, layoutHeight) {
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).map { val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
// Tachiyomi: layoutWidth after horizontal insets
val insetLayoutWidth = layoutWidth - leftInset - rightInset
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
it.measure(topBarConstraints) it.measure(topBarConstraints)
} }
val topBarHeight = topBarPlaceables.maxByOrNull { it.height }?.height ?: 0 val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).map { val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
it.measure(looseConstraints) it.measure(looseConstraints)
} }
val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0 val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarWidth = snackbarPlaceables.maxByOrNull { it.width }?.width ?: 0 val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
// Tachiyomi: Calculate insets for snackbar placement offset
val snackbarLeft = if (snackbarPlaceables.isNotEmpty()) {
(insetLayoutWidth - snackbarWidth) / 2 + leftInset
} else {
0
}
val fabPlaceables = val fabPlaceables =
subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable -> subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 } measurable.measure(looseConstraints)
} }
val fabHeight = fabPlaceables.maxByOrNull { it.height }?.height ?: 0 val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
val fabPlacement = if (fabPlaceables.isNotEmpty()) { val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) {
val fabWidth = fabPlaceables.maxByOrNull { it.width }!!.width
// FAB distance from the left of the layout, taking into account LTR / RTL // FAB distance from the left of the layout, taking into account LTR / RTL
// Tachiyomi: Calculate insets for fab placement offset
val fabLeftOffset = if (fabPosition == FabPosition.End) { val fabLeftOffset = if (fabPosition == FabPosition.End) {
if (layoutDirection == LayoutDirection.Ltr) { if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset
} else { } else {
FabSpacing.roundToPx() FabSpacing.roundToPx() + leftInset
} }
} else { } else {
(layoutWidth - fabWidth) / 2 leftInset + ((insetLayoutWidth - fabWidth) / 2)
} }
FabPlacement( FabPlacement(
@ -190,75 +218,63 @@ private fun ScaffoldLayout(
LocalFabPlacement provides fabPlacement, LocalFabPlacement provides fabPlacement,
content = bottomBar, content = bottomBar,
) )
}.map { it.measure(looseConstraints) } }.fastMap { it.measure(looseConstraints) }
val bottomBarHeight = bottomBarPlaceables.maxByOrNull { it.height }?.height ?: 0 val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
val fabOffsetFromBottom = fabPlacement?.let { val fabOffsetFromBottom = fabPlacement?.let {
if (bottomBarHeight == 0) { max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
it.height + FabSpacing.roundToPx()
} else {
// Total height is the bottom bar height + the FAB height + the padding
// between the FAB and bottom bar
bottomBarHeight + it.height + FabSpacing.roundToPx()
}
} }
val snackbarOffsetFromBottom = if (snackbarHeight != 0) { val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight) snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
} else { } else {
0 0
} }
/**
* Tachiyomi: Also take account of fab height when providing inner padding
*/
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) { val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
val insets = WindowInsets.Companion.safeDrawing val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
.asPaddingValues(this@SubcomposeLayout) val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp
val bottomBarHeightPx = bottomBarHeight ?: 0
val innerPadding = PaddingValues( val innerPadding = PaddingValues(
top = top =
if (topBarHeight == 0) { if (topBarPlaceables.isEmpty()) {
insets.calculateTopPadding() insets.calculateTopPadding()
} else { } else {
topBarHeight.toDp() topBarHeight.toDp()
}, },
bottom = bottom = // Tachiyomi: Also take account of fab height when providing inner padding
( if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
if (bottomBarHeight == 0) { max(insets.calculateBottomPadding(), fabOffsetDp)
insets.calculateBottomPadding() } else {
} else { max(bottomBarHeightPx.toDp(), fabOffsetDp)
bottomBarHeight.toDp() },
} start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
) + fabHeight.toDp(), end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection),
) )
content(innerPadding) content(innerPadding)
}.map { it.measure(looseConstraints) } }.fastMap { it.measure(looseConstraints) }
// Placing to control drawing order to match default elevation of each placeable // Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.forEach { bodyContentPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
topBarPlaceables.forEach { topBarPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
snackbarPlaceables.forEach { snackbarPlaceables.fastForEach {
it.place( it.place(
(layoutWidth - snackbarWidth) / 2, snackbarLeft,
layoutHeight - snackbarOffsetFromBottom, layoutHeight - snackbarOffsetFromBottom,
) )
} }
// The bottom bar is always at the bottom of the layout // The bottom bar is always at the bottom of the layout
bottomBarPlaceables.forEach { bottomBarPlaceables.fastForEach {
it.place(0, layoutHeight - bottomBarHeight) it.place(0, layoutHeight - (bottomBarHeight ?: 0))
} }
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
fabPlacement?.let { placement -> fabPlaceables.fastForEach {
fabPlaceables.forEach { it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
}
} }
} }
} }

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -16,8 +17,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -27,7 +26,6 @@ fun TabbedScreen(
tabs: List<TabContent>, tabs: List<TabContent>,
startIndex: Int? = null, startIndex: Int? = null,
searchQuery: String? = null, searchQuery: String? = null,
@StringRes placeholderRes: Int? = null,
onChangeSearchQuery: (String?) -> Unit = {}, onChangeSearchQuery: (String?) -> Unit = {},
incognitoMode: Boolean, incognitoMode: Boolean,
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,
@ -43,28 +41,16 @@ fun TabbedScreen(
Scaffold( Scaffold(
topBar = { topBar = {
if (searchQuery == null) { val tab = tabs[state.currentPage]
AppBar( val searchEnabled = tab.searchEnabled
title = stringResource(titleRes),
actions = { SearchToolbar(
AppBarActions(tabs[state.currentPage].actions) titleContent = { AppBarTitle(stringResource(titleRes)) },
}, searchEnabled = searchEnabled,
) searchQuery = if (searchEnabled) searchQuery else null,
} else { onChangeSearchQuery = onChangeSearchQuery,
SearchToolbar( actions = { AppBarActions(tab.actions) },
searchQuery = searchQuery, )
placeholderText = placeholderRes?.let { stringResource(it) },
onChangeSearchQuery = {
onChangeSearchQuery(it)
},
onClickCloseSearch = {
onChangeSearchQuery(null)
},
onClickResetSearch = {
onChangeSearchQuery("")
},
)
}
}, },
) { contentPadding -> ) { contentPadding ->
Column( Column(
@ -82,9 +68,8 @@ fun TabbedScreen(
Tab( Tab(
selected = state.currentPage == index, selected = state.currentPage == index,
onClick = { scope.launch { state.animateScrollToPage(index) } }, onClick = { scope.launch { state.animateScrollToPage(index) } },
text = { text = { TabText(text = stringResource(tab.titleRes), badgeCount = tab.badgeNumber) },
TabText(stringResource(tab.titleRes), tab.badgeNumber, state.currentPage == index) unselectedContentColor = MaterialTheme.colorScheme.onSurface,
},
) )
} }
} }
@ -110,6 +95,7 @@ fun TabbedScreen(
data class TabContent( data class TabContent(
@StringRes val titleRes: Int, @StringRes val titleRes: Int,
val badgeNumber: Int? = null, val badgeNumber: Int? = null,
val searchEnabled: Boolean = false,
val actions: List<AppBar.Action> = emptyList(), val actions: List<AppBar.Action> = emptyList(),
val content: @Composable (contentPadding: PaddingValues) -> Unit, val content: @Composable (contentPadding: PaddingValues) -> Unit,
) )

View File

@ -30,17 +30,13 @@ fun TabIndicator(currentTabPosition: TabPosition) {
fun TabText( fun TabText(
text: String, text: String,
badgeCount: Int? = null, badgeCount: Int? = null,
isCurrentPage: Boolean,
) { ) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(text = text)
text = text,
color = if (isCurrentPage) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
)
if (badgeCount != null) { if (badgeCount != null) {
Pill( Pill(
text = "$badgeCount", text = "$badgeCount",

View File

@ -7,10 +7,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.RelativeDateHeader import eu.kanade.presentation.components.RelativeDateHeader
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.history.HistoryUiModel import eu.kanade.presentation.history.HistoryUiModel
import eu.kanade.presentation.util.plus
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DateFormat import java.text.DateFormat
@ -27,7 +26,7 @@ fun HistoryContent(
val relativeTime: Int = remember { preferences.relativeTime().get() } val relativeTime: Int = remember { preferences.relativeTime().get() }
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
ScrollbarLazyColumn( FastScrollLazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
items( items(

View File

@ -1,19 +1,13 @@
package eu.kanade.presentation.history.components package eu.kanade.presentation.history.components
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
@ -26,54 +20,12 @@ fun HistoryToolbar(
incognitoMode: Boolean, incognitoMode: Boolean,
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current SearchToolbar(
val focusManager = LocalFocusManager.current titleContent = { AppBarTitle(stringResource(R.string.history)) },
searchQuery = state.searchQuery,
if (state.searchQuery == null) { onChangeSearchQuery = { state.searchQuery = it },
HistoryRegularToolbar(
onClickSearch = { state.searchQuery = "" },
onClickDelete = { state.dialog = HistoryPresenter.Dialog.DeleteAll },
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
scrollBehavior = scrollBehavior,
)
} else {
SearchToolbar(
searchQuery = state.searchQuery!!,
onChangeSearchQuery = { state.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint),
onClickCloseSearch = { state.searchQuery = null },
onClickResetSearch = { state.searchQuery = "" },
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
keyboardController?.hide()
},
),
)
}
}
@Composable
fun HistoryRegularToolbar(
onClickSearch: () -> Unit,
onClickDelete: () -> Unit,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
scrollBehavior: TopAppBarScrollBehavior,
) {
AppBar(
title = stringResource(R.string.history),
actions = { actions = {
IconButton(onClick = onClickSearch) { IconButton(onClick = { state.dialog = HistoryPresenter.Dialog.DeleteAll }) {
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
}
IconButton(onClick = onClickDelete) {
Icon(Icons.Outlined.DeleteSweep, contentDescription = stringResource(R.string.pref_clear_history)) Icon(Icons.Outlined.DeleteSweep, contentDescription = stringResource(R.string.pref_clear_history))
} }
}, },

View File

@ -6,7 +6,10 @@ import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.util.fastAll
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.display import eu.kanade.domain.library.model.display
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
@ -17,6 +20,7 @@ import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryPresenter import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@ -29,14 +33,17 @@ fun LibraryScreen(
onChangeCategoryClicked: () -> Unit, onChangeCategoryClicked: () -> Unit,
onMarkAsReadClicked: () -> Unit, onMarkAsReadClicked: () -> Unit,
onMarkAsUnreadClicked: () -> Unit, onMarkAsUnreadClicked: () -> Unit,
onDownloadClicked: () -> Unit, onDownloadClicked: (DownloadAction) -> Unit,
onDeleteClicked: () -> Unit, onDeleteClicked: () -> Unit,
onClickUnselectAll: () -> Unit, onClickUnselectAll: () -> Unit,
onClickSelectAll: () -> Unit, onClickSelectAll: () -> Unit,
onClickInvertSelection: () -> Unit, onClickInvertSelection: () -> Unit,
onClickFilter: () -> Unit, onClickFilter: () -> Unit,
onClickRefresh: (Category?) -> Boolean, onClickRefresh: (Category?) -> Boolean,
onClickOpenRandomManga: () -> Unit,
) { ) {
val haptic = LocalHapticFeedback.current
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
val title by presenter.getToolbarTitle() val title by presenter.getToolbarTitle()
@ -51,6 +58,7 @@ fun LibraryScreen(
onClickInvertSelection = onClickInvertSelection, onClickInvertSelection = onClickInvertSelection,
onClickFilter = onClickFilter, onClickFilter = onClickFilter,
onClickRefresh = { onClickRefresh(null) }, onClickRefresh = { onClickRefresh(null) },
onClickOpenRandomManga = onClickOpenRandomManga,
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
) )
}, },
@ -60,7 +68,7 @@ fun LibraryScreen(
onChangeCategoryClicked = onChangeCategoryClicked, onChangeCategoryClicked = onChangeCategoryClicked,
onMarkAsReadClicked = onMarkAsReadClicked, onMarkAsReadClicked = onMarkAsReadClicked,
onMarkAsUnreadClicked = onMarkAsUnreadClicked, onMarkAsUnreadClicked = onMarkAsUnreadClicked,
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.none { it.manga.isLocal() } }, onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.fastAll { !it.manga.isLocal() } },
onDeleteClicked = onDeleteClicked, onDeleteClicked = onDeleteClicked,
) )
}, },
@ -97,7 +105,10 @@ fun LibraryScreen(
onChangeCurrentPage = { presenter.activeCategory = it }, onChangeCurrentPage = { presenter.activeCategory = it },
onMangaClicked = onMangaClicked, onMangaClicked = onMangaClicked,
onToggleSelection = { presenter.toggleSelection(it) }, onToggleSelection = { presenter.toggleSelection(it) },
onToggleRangeSelection = { presenter.toggleRangeSelection(it) }, onToggleRangeSelection = {
presenter.toggleRangeSelection(it)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onRefresh = onClickRefresh, onRefresh = onClickRefresh,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,
getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) }, getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) },

View File

@ -15,12 +15,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.SwipeRefresh import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.rememberPagerState
import eu.kanade.presentation.library.LibraryState import eu.kanade.presentation.library.LibraryState
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -68,12 +68,13 @@ fun LibraryContent(
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) { if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
LibraryTabs( LibraryTabs(
state = pagerState,
categories = categories, categories = categories,
currentPageIndex = pagerState.currentPage,
showMangaCount = showMangaCount, showMangaCount = showMangaCount,
getNumberOfMangaForCategory = getNumberOfMangaForCategory, getNumberOfMangaForCategory = getNumberOfMangaForCategory,
isDownloadOnly = isDownloadOnly, isDownloadOnly = isDownloadOnly,
isIncognitoMode = isIncognitoMode, isIncognitoMode = isIncognitoMode,
onTabItemClick = { scope.launch { pagerState.animateScrollToPage(it) } },
) )
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -39,7 +40,10 @@ fun LibraryList(
) { ) {
item { item {
if (searchQuery.isNullOrEmpty().not()) { if (searchQuery.isNullOrEmpty().not()) {
TextButton(onClick = onGlobalSearchClicked) { TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onGlobalSearchClicked,
) {
Text( Text(
text = stringResource(R.string.action_global_search_query, searchQuery!!), text = stringResource(R.string.action_global_search_query, searchQuery!!),
modifier = Modifier.zIndex(99f), modifier = Modifier.zIndex(99f),

View File

@ -10,11 +10,11 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.HorizontalPager
import eu.kanade.presentation.components.PagerState
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable @Composable

View File

@ -1,56 +1,54 @@
package eu.kanade.presentation.library.components package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.PagerState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.TabIndicator import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText import eu.kanade.presentation.components.TabText
import kotlinx.coroutines.launch
@Composable @Composable
fun LibraryTabs( fun LibraryTabs(
state: PagerState,
categories: List<Category>, categories: List<Category>,
currentPageIndex: Int,
showMangaCount: Boolean, showMangaCount: Boolean,
isDownloadOnly: Boolean, isDownloadOnly: Boolean,
isIncognitoMode: Boolean, isIncognitoMode: Boolean,
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>, getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
onTabItemClick: (Int) -> Unit,
) { ) {
val scope = rememberCoroutineScope()
Column { Column {
ScrollableTabRow( ScrollableTabRow(
selectedTabIndex = state.currentPage, selectedTabIndex = currentPageIndex,
edgePadding = 0.dp, edgePadding = 0.dp,
indicator = { TabIndicator(it[state.currentPage]) }, indicator = { TabIndicator(it[currentPageIndex]) },
// TODO: use default when width is fixed upstream // TODO: use default when width is fixed upstream
// https://issuetracker.google.com/issues/242879624 // https://issuetracker.google.com/issues/242879624
divider = {}, divider = {},
) { ) {
categories.forEachIndexed { index, category -> categories.forEachIndexed { index, category ->
val count by if (showMangaCount) {
getNumberOfMangaForCategory(category.id)
} else {
remember { mutableStateOf<Int?>(null) }
}
Tab( Tab(
selected = state.currentPage == index, selected = currentPageIndex == index,
onClick = { scope.launch { state.animateScrollToPage(index) } }, onClick = { onTabItemClick(index) },
text = { text = {
TabText(category.visualName, count, state.currentPage == index) TabText(
text = category.visualName,
badgeCount = if (showMangaCount) {
getNumberOfMangaForCategory(category.id)
} else {
null
}?.value,
)
}, },
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
) )
} }
} }

View File

@ -2,14 +2,11 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
@ -19,13 +16,11 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.components.Pill import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.library.LibraryState import eu.kanade.presentation.library.LibraryState
@ -43,6 +38,7 @@ fun LibraryToolbar(
onClickInvertSelection: () -> Unit, onClickInvertSelection: () -> Unit,
onClickFilter: () -> Unit, onClickFilter: () -> Unit,
onClickRefresh: () -> Unit, onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?, scrollBehavior: TopAppBarScrollBehavior?,
) = when { ) = when {
state.selectionMode -> LibrarySelectionToolbar( state.selectionMode -> LibrarySelectionToolbar(
@ -53,38 +49,16 @@ fun LibraryToolbar(
onClickSelectAll = onClickSelectAll, onClickSelectAll = onClickSelectAll,
onClickInvertSelection = onClickInvertSelection, onClickInvertSelection = onClickInvertSelection,
) )
state.searchQuery != null -> {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
SearchToolbar(
searchQuery = state.searchQuery!!,
onChangeSearchQuery = { state.searchQuery = it },
onClickCloseSearch = { state.searchQuery = null },
onClickResetSearch = { state.searchQuery = "" },
scrollBehavior = scrollBehavior,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
placeholderText = stringResource(R.string.action_search_hint),
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
keyboardController?.hide()
},
),
)
}
else -> LibraryRegularToolbar( else -> LibraryRegularToolbar(
title = title, title = title,
hasFilters = state.hasActiveFilters, hasFilters = state.hasActiveFilters,
incognitoMode = incognitoMode, incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode, downloadedOnlyMode = downloadedOnlyMode,
onClickSearch = { state.searchQuery = "" }, searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
onClickFilter = onClickFilter, onClickFilter = onClickFilter,
onClickRefresh = onClickRefresh, onClickRefresh = onClickRefresh,
onClickOpenRandomManga = onClickOpenRandomManga,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
} }
@ -95,14 +69,15 @@ fun LibraryRegularToolbar(
hasFilters: Boolean, hasFilters: Boolean,
incognitoMode: Boolean, incognitoMode: Boolean,
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,
onClickSearch: () -> Unit, searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
onClickFilter: () -> Unit, onClickFilter: () -> Unit,
onClickRefresh: () -> Unit, onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?, scrollBehavior: TopAppBarScrollBehavior?,
) { ) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current SearchToolbar(
AppBar(
titleContent = { titleContent = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
@ -120,15 +95,29 @@ fun LibraryRegularToolbar(
} }
} }
}, },
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
actions = { actions = {
IconButton(onClick = onClickSearch) { val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
}
IconButton(onClick = onClickFilter) { IconButton(onClick = onClickFilter) {
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint) Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
} }
IconButton(onClick = onClickRefresh) {
Icon(Icons.Outlined.Refresh, contentDescription = stringResource(R.string.pref_category_library_update)) OverflowMenu { closeMenu ->
DropdownMenuItem(
text = { Text(text = stringResource(R.string.pref_category_library_update)) },
onClick = {
onClickRefresh()
closeMenu()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_open_random_manga)) },
onClick = {
onClickOpenRandomManga()
closeMenu()
},
)
} }
}, },
incognitoMode = incognitoMode, incognitoMode = incognitoMode,

View File

@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
@ -35,6 +34,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@ -43,6 +43,9 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastMap
import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadAction
import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.ExtendedFloatingActionButton
@ -204,7 +207,7 @@ private fun MangaScreenSmallImpl(
val chapters = remember(state) { state.processedChapters.toList() } val chapters = remember(state) { state.processedChapters.toList() }
val internalOnBackPressed = { val internalOnBackPressed = {
if (chapters.any { it.selected }) { if (chapters.fastAny { it.selected }) {
onAllChapterSelected(false) onAllChapterSelected(false)
} else { } else {
onBackClicked() onBackClicked()
@ -213,8 +216,6 @@ private fun MangaScreenSmallImpl(
BackHandler(onBack = internalOnBackPressed) BackHandler(onBack = internalOnBackPressed)
Scaffold( Scaffold(
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal).asPaddingValues()),
topBar = { topBar = {
val firstVisibleItemIndex by remember { val firstVisibleItemIndex by remember {
derivedStateOf { chapterListState.firstVisibleItemIndex } derivedStateOf { chapterListState.firstVisibleItemIndex }
@ -260,13 +261,13 @@ private fun MangaScreenSmallImpl(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
floatingActionButton = { floatingActionButton = {
AnimatedVisibility( AnimatedVisibility(
visible = chapters.any { !it.chapter.read } && chapters.none { it.selected }, visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { text = {
val id = if (chapters.any { it.chapter.read }) { val id = if (chapters.fastAny { it.chapter.read }) {
R.string.action_resume R.string.action_resume
} else { } else {
R.string.action_start R.string.action_start
@ -276,8 +277,6 @@ private fun MangaScreenSmallImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
) )
} }
}, },
@ -287,17 +286,21 @@ private fun MangaScreenSmallImpl(
SwipeRefresh( SwipeRefresh(
refreshing = state.isRefreshingData, refreshing = state.isRefreshingData,
onRefresh = onRefresh, onRefresh = onRefresh,
enabled = chapters.none { it.selected }, enabled = chapters.fastAll { !it.selected },
indicatorPadding = contentPadding, indicatorPadding = contentPadding,
) { ) {
val layoutDirection = LocalLayoutDirection.current
VerticalFastScroller( VerticalFastScroller(
listState = chapterListState, listState = chapterListState,
topContentPadding = topPadding, topContentPadding = topPadding,
endContentPadding = contentPadding.calculateEndPadding(layoutDirection),
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
state = chapterListState, state = chapterListState,
contentPadding = PaddingValues( contentPadding = PaddingValues(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding(), bottom = contentPadding.calculateBottomPadding(),
), ),
) { ) {
@ -351,6 +354,7 @@ private fun MangaScreenSmallImpl(
contentType = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER,
) { ) {
ChapterHeader( ChapterHeader(
enabled = chapters.fastAll { !it.selected },
chapterCount = chapters.size, chapterCount = chapters.size,
onClick = onFilterClicked, onClick = onFilterClicked,
) )
@ -410,11 +414,11 @@ fun MangaScreenLargeImpl(
val chapters = remember(state) { state.processedChapters.toList() } val chapters = remember(state) { state.processedChapters.toList() }
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) } var topBarHeight by remember { mutableStateOf(0) }
SwipeRefresh( SwipeRefresh(
refreshing = state.isRefreshingData, refreshing = state.isRefreshingData,
onRefresh = onRefresh, onRefresh = onRefresh,
enabled = chapters.none { it.selected }, enabled = chapters.fastAll { !it.selected },
indicatorPadding = PaddingValues( indicatorPadding = PaddingValues(
start = insetPadding.calculateStartPadding(layoutDirection), start = insetPadding.calculateStartPadding(layoutDirection),
top = with(density) { topBarHeight.toDp() }, top = with(density) { topBarHeight.toDp() },
@ -424,7 +428,7 @@ fun MangaScreenLargeImpl(
val chapterListState = rememberLazyListState() val chapterListState = rememberLazyListState()
val internalOnBackPressed = { val internalOnBackPressed = {
if (chapters.any { it.selected }) { if (chapters.fastAny { it.selected }) {
onAllChapterSelected(false) onAllChapterSelected(false)
} else { } else {
onBackClicked() onBackClicked()
@ -433,12 +437,11 @@ fun MangaScreenLargeImpl(
BackHandler(onBack = internalOnBackPressed) BackHandler(onBack = internalOnBackPressed)
Scaffold( Scaffold(
modifier = Modifier.padding(insetPadding),
topBar = { topBar = {
MangaToolbar( MangaToolbar(
modifier = Modifier.onSizeChanged { onTopBarHeightChanged(it.height) }, modifier = Modifier.onSizeChanged { topBarHeight = it.height },
title = state.manga.title, title = state.manga.title,
titleAlphaProvider = { if (chapters.any { it.selected }) 1f else 0f }, titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f },
backgroundAlphaProvider = { 1f }, backgroundAlphaProvider = { 1f },
hasFilters = state.manga.chaptersFiltered(), hasFilters = state.manga.chaptersFiltered(),
incognitoMode = state.isIncognitoMode, incognitoMode = state.isIncognitoMode,
@ -473,13 +476,13 @@ fun MangaScreenLargeImpl(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
floatingActionButton = { floatingActionButton = {
AnimatedVisibility( AnimatedVisibility(
visible = chapters.any { !it.chapter.read } && chapters.none { it.selected }, visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { text = {
val id = if (chapters.any { it.chapter.read }) { val id = if (chapters.fastAny { it.chapter.read }) {
R.string.action_resume R.string.action_resume
} else { } else {
R.string.action_start R.string.action_start
@ -489,17 +492,20 @@ fun MangaScreenLargeImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
) )
} }
}, },
) { contentPadding -> ) { contentPadding ->
TwoPanelBox( TwoPanelBox(
modifier = Modifier.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
),
startContent = { startContent = {
Column( Column(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState())
.padding(bottom = contentPadding.calculateBottomPadding()),
) { ) {
MangaInfoBox( MangaInfoBox(
isTabletUi = true, isTabletUi = true,
@ -548,6 +554,7 @@ fun MangaScreenLargeImpl(
contentType = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER,
) { ) {
ChapterHeader( ChapterHeader(
enabled = chapters.fastAll { !it.selected },
chapterCount = chapters.size, chapterCount = chapters.size,
onClick = onFilterButtonClicked, onClick = onFilterButtonClicked,
) )
@ -582,29 +589,29 @@ private fun SharedMangaBottomActionMenu(
visible = selected.isNotEmpty(), visible = selected.isNotEmpty(),
modifier = modifier.fillMaxWidth(fillFraction), modifier = modifier.fillMaxWidth(fillFraction),
onBookmarkClicked = { onBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected.map { it.chapter }, true) onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, true)
}.takeIf { selected.any { !it.chapter.bookmark } }, }.takeIf { selected.fastAny { !it.chapter.bookmark } },
onRemoveBookmarkClicked = { onRemoveBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected.map { it.chapter }, false) onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, false)
}.takeIf { selected.all { it.chapter.bookmark } }, }.takeIf { selected.fastAll { it.chapter.bookmark } },
onMarkAsReadClicked = { onMarkAsReadClicked = {
onMultiMarkAsReadClicked(selected.map { it.chapter }, true) onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, true)
}.takeIf { selected.any { !it.chapter.read } }, }.takeIf { selected.fastAny { !it.chapter.read } },
onMarkAsUnreadClicked = { onMarkAsUnreadClicked = {
onMultiMarkAsReadClicked(selected.map { it.chapter }, false) onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, false)
}.takeIf { selected.any { it.chapter.read || it.chapter.lastPageRead > 0L } }, }.takeIf { selected.fastAny { it.chapter.read || it.chapter.lastPageRead > 0L } },
onMarkPreviousAsReadClicked = { onMarkPreviousAsReadClicked = {
onMarkPreviousAsReadClicked(selected[0].chapter) onMarkPreviousAsReadClicked(selected[0].chapter)
}.takeIf { selected.size == 1 }, }.takeIf { selected.size == 1 },
onDownloadClicked = { onDownloadClicked = {
onDownloadChapter!!(selected.toList(), ChapterDownloadAction.START) onDownloadChapter!!(selected.toList(), ChapterDownloadAction.START)
}.takeIf { }.takeIf {
onDownloadChapter != null && selected.any { it.downloadState != Download.State.DOWNLOADED } onDownloadChapter != null && selected.fastAny { it.downloadState != Download.State.DOWNLOADED }
}, },
onDeleteClicked = { onDeleteClicked = {
onMultiDeleteClicked(selected.map { it.chapter }) onMultiDeleteClicked(selected.fastMap { it.chapter })
}.takeIf { }.takeIf {
onDownloadChapter != null && selected.any { it.downloadState == Download.State.DOWNLOADED } onDownloadChapter != null && selected.fastAny { it.downloadState == Download.State.DOWNLOADED }
}, },
) )
} }
@ -629,6 +636,7 @@ private fun LazyListScope.sharedChapterItems(
read = chapterItem.chapter.read, read = chapterItem.chapter.read,
bookmark = chapterItem.chapter.bookmark, bookmark = chapterItem.chapter.bookmark,
selected = chapterItem.selected, selected = chapterItem.selected,
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
downloadStateProvider = { chapterItem.downloadState }, downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress }, downloadProgressProvider = { chapterItem.downloadProgress },
onLongClick = { onLongClick = {
@ -660,7 +668,7 @@ private fun onChapterItemClick(
) { ) {
when { when {
chapterItem.selected -> onToggleSelection(false) chapterItem.selected -> onToggleSelection(false)
chapters.any { it.selected } -> onToggleSelection(true) chapters.fastAny { it.selected } -> onToggleSelection(true)
else -> onChapterClicked(chapterItem.chapter) else -> onChapterClicked(chapterItem.chapter)
} }
} }

View File

@ -16,13 +16,17 @@ import eu.kanade.tachiyomi.R
@Composable @Composable
fun ChapterHeader( fun ChapterHeader(
enabled: Boolean,
chapterCount: Int?, chapterCount: Int?,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(
enabled = enabled,
onClick = onClick,
)
.padding(horizontal = 16.dp, vertical = 4.dp), .padding(horizontal = 16.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {

View File

@ -1,6 +1,5 @@
package eu.kanade.presentation.manga.components package eu.kanade.presentation.manga.components
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -23,7 +22,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -32,6 +30,8 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadAction
import eu.kanade.presentation.components.ChapterDownloadIndicator import eu.kanade.presentation.components.ChapterDownloadIndicator
import eu.kanade.presentation.util.ReadItemAlpha import eu.kanade.presentation.util.ReadItemAlpha
import eu.kanade.presentation.util.SecondaryItemAlpha
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -45,6 +45,7 @@ fun MangaChapterListItem(
read: Boolean, read: Boolean,
bookmark: Boolean, bookmark: Boolean,
selected: Boolean, selected: Boolean,
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State, downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
onLongClick: () -> Unit, onLongClick: () -> Unit,
@ -53,7 +54,7 @@ fun MangaChapterListItem(
) { ) {
Row( Row(
modifier = modifier modifier = modifier
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent) .selectedBackground(selected)
.combinedClickable( .combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
@ -67,6 +68,7 @@ fun MangaChapterListItem(
MaterialTheme.colorScheme.onSurface MaterialTheme.colorScheme.onSurface
} }
val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f } val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f }
val textSubtitleAlpha = remember(read) { if (read) ReadItemAlpha else SecondaryItemAlpha }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
var textHeight by remember { mutableStateOf(0) } var textHeight by remember { mutableStateOf(0) }
@ -91,7 +93,7 @@ fun MangaChapterListItem(
) )
} }
Spacer(modifier = Modifier.height(6.dp)) Spacer(modifier = Modifier.height(6.dp))
Row(modifier = Modifier.alpha(textAlpha)) { Row(modifier = Modifier.alpha(textSubtitleAlpha)) {
ProvideTextStyle( ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium value = MaterialTheme.typography.bodyMedium
.copy(color = textColor, fontSize = 12.sp), .copy(color = textColor, fontSize = 12.sp),
@ -127,6 +129,7 @@ fun MangaChapterListItem(
// Download view // Download view
if (onDownloadClick != null) { if (onDownloadClick != null) {
ChapterDownloadIndicator( ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp), modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider, downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider, downloadProgressProvider = downloadProgressProvider,

View File

@ -24,11 +24,14 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -82,9 +85,15 @@ fun MangaCoverDialog(
} }
if (onEditClick != null) { if (onEditClick != null) {
Box { Box {
val (expanded, onExpand) = remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
IconButton( IconButton(
onClick = { if (isCustomCover) onExpand(true) else onEditClick(EditCoverAction.EDIT) }, onClick = {
if (isCustomCover) {
expanded = true
} else {
onEditClick(EditCoverAction.EDIT)
}
},
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.Edit, imageVector = Icons.Outlined.Edit,
@ -93,20 +102,21 @@ fun MangaCoverDialog(
} }
DropdownMenu( DropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = { onExpand(false) }, onDismissRequest = { expanded = false },
offset = DpOffset(8.dp, 0.dp),
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_edit)) }, text = { Text(text = stringResource(R.string.action_edit)) },
onClick = { onClick = {
onEditClick(EditCoverAction.EDIT) onEditClick(EditCoverAction.EDIT)
onExpand(false) expanded = false
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_delete)) }, text = { Text(text = stringResource(R.string.action_delete)) },
onClick = { onClick = {
onEditClick(EditCoverAction.DELETE) onEditClick(EditCoverAction.DELETE)
onExpand(false) expanded = false
}, },
) )
} }

View File

@ -2,15 +2,12 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -30,7 +27,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.theme.active import eu.kanade.presentation.theme.active
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -101,53 +99,11 @@ fun MangaToolbar(
) )
} }
val onDismissRequest = { onDownloadExpanded(false) } val onDismissRequest = { onDownloadExpanded(false) }
DropdownMenu( DownloadDropdownMenu(
expanded = downloadExpanded, expanded = downloadExpanded,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { onDownloadClicked = onClickDownload,
DropdownMenuItem( )
text = { Text(text = stringResource(R.string.download_1)) },
onClick = {
onClickDownload(DownloadAction.NEXT_1_CHAPTER)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_5)) },
onClick = {
onClickDownload(DownloadAction.NEXT_5_CHAPTERS)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_10)) },
onClick = {
onClickDownload(DownloadAction.NEXT_10_CHAPTERS)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_custom)) },
onClick = {
onClickDownload(DownloadAction.CUSTOM)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_unread)) },
onClick = {
onClickDownload(DownloadAction.UNREAD_CHAPTERS)
onDismissRequest()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download_all)) },
onClick = {
onClickDownload(DownloadAction.ALL_CHAPTERS)
onDismissRequest()
},
)
}
} }
} }
@ -156,49 +112,39 @@ fun MangaToolbar(
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint) Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
} }
if (onClickEditCategory != null && onClickMigrate != null) { if (onClickEditCategory != null || onClickMigrate != null || onClickShare != null) {
val (moreExpanded, onMoreExpanded) = remember { mutableStateOf(false) } OverflowMenu { closeMenu ->
Box { if (onClickEditCategory != null) {
IconButton(onClick = { onMoreExpanded(!moreExpanded) }) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
)
}
val onDismissRequest = { onMoreExpanded(false) }
DropdownMenu(
expanded = moreExpanded,
onDismissRequest = onDismissRequest,
) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_edit_categories)) }, text = { Text(text = stringResource(R.string.action_edit_categories)) },
onClick = { onClick = {
onClickEditCategory() onClickEditCategory()
onDismissRequest() closeMenu()
}, },
) )
}
if (onClickMigrate != null) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_migrate)) }, text = { Text(text = stringResource(R.string.action_migrate)) },
onClick = { onClick = {
onClickMigrate() onClickMigrate()
onDismissRequest() closeMenu()
},
)
}
if (onClickShare != null) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_share)) },
onClick = {
onClickShare()
closeMenu()
}, },
) )
if (onClickShare != null) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_share)) },
onClick = {
onClickShare()
onDismissRequest()
},
)
}
} }
} }
} }
} }
}, },
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme containerColor = MaterialTheme.colorScheme
.surfaceColorAtElevation(3.dp) .surfaceColorAtElevation(3.dp)

View File

@ -1,5 +1,9 @@
package eu.kanade.presentation.more package eu.kanade.presentation.more
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CloudOff import androidx.compose.material.icons.outlined.CloudOff
@ -21,6 +25,7 @@ import androidx.compose.ui.res.vectorResource
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -32,6 +37,7 @@ import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@Composable @Composable
fun MoreScreen( fun MoreScreen(
presenter: MorePresenter, presenter: MorePresenter,
isFDroid: Boolean,
onClickDownloadQueue: () -> Unit, onClickDownloadQueue: () -> Unit,
onClickCategories: () -> Unit, onClickCategories: () -> Unit,
onClickBackupAndRestore: () -> Unit, onClickBackupAndRestore: () -> Unit,
@ -43,8 +49,21 @@ fun MoreScreen(
ScrollbarLazyColumn( ScrollbarLazyColumn(
modifier = Modifier.statusBarsPadding(), modifier = Modifier.statusBarsPadding(),
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(), contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
WindowInsets.navigationBars.asPaddingValues(),
),
) { ) {
if (isFDroid) {
item {
WarningBanner(
textRes = R.string.fdroid_warning,
modifier = Modifier.clickable {
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
},
)
}
}
item { item {
LogoHeader() LogoHeader()
} }

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMap
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
@ -240,14 +241,14 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
fun selectAll() = mutableState.update { state -> fun selectAll() = mutableState.update { state ->
if (state !is State.Ready) return@update state if (state !is State.Ready) return@update state
state.copy(selection = state.items.map { it.id }) state.copy(selection = state.items.fastMap { it.id })
} }
fun invertSelection() = mutableState.update { state -> fun invertSelection() = mutableState.update { state ->
if (state !is State.Ready) return@update state if (state !is State.Ready) return@update state
state.copy( state.copy(
selection = state.items selection = state.items
.map { it.id } .fastMap { it.id }
.filterNot { it in state.selection }, .filterNot { it in state.selection },
) )
} }

View File

@ -16,6 +16,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastMap
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
@ -204,8 +205,8 @@ class SettingsDownloadScreen : SearchableSettings {
itemLabel = { it.visualName }, itemLabel = { it.visualName },
onDismissRequest = { showDialog = false }, onDismissRequest = { showDialog = false },
onValueChanged = { newIncluded, newExcluded -> onValueChanged = { newIncluded, newExcluded ->
downloadNewChapterCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet()) downloadNewChapterCategoriesPref.set(newIncluded.fastMap { it.id.toString() }.toSet())
downloadNewChapterCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet()) downloadNewChapterCategoriesExcludePref.set(newExcluded.fastMap { it.id.toString() }.toSet())
showDialog = false showDialog = false
}, },
) )

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastMap
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
@ -124,9 +125,9 @@ class SettingsLibraryScreen : SearchableSettings {
// For default category // For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) + val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
allCategories.map { it.id.toInt() } allCategories.fastMap { it.id.toInt() }
val labels = listOf(stringResource(R.string.default_category_summary)) + val labels = listOf(stringResource(R.string.default_category_summary)) +
allCategories.map { it.visualName(context) } allCategories.fastMap { it.visualName(context) }
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(R.string.categories), title = stringResource(R.string.categories),

View File

@ -44,43 +44,42 @@ internal fun BasePreferenceWidget(
widget: @Composable (() -> Unit)? = null, widget: @Composable (() -> Unit)? = null,
) { ) {
val highlighted = LocalPreferenceHighlighted.current val highlighted = LocalPreferenceHighlighted.current
Box(modifier = Modifier.highlightBackground(highlighted)) { Row(
Row( modifier = modifier
modifier = modifier .highlightBackground(highlighted)
.sizeIn(minHeight = 56.dp) .sizeIn(minHeight = 56.dp)
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) .clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) {
if (icon != null) {
Box(
modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp),
content = { icon() },
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = PrefsVerticalPadding),
) { ) {
if (icon != null) { if (!title.isNullOrBlank()) {
Box( Text(
modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp), modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
content = { icon() }, text = title,
) overflow = TextOverflow.Ellipsis,
} maxLines = 2,
Column( style = MaterialTheme.typography.titleLarge,
modifier = Modifier fontSize = TitleFontSize,
.weight(1f)
.padding(vertical = PrefsVerticalPadding),
) {
if (!title.isNullOrBlank()) {
Text(
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
text = title,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
style = MaterialTheme.typography.titleLarge,
fontSize = TitleFontSize,
)
}
subcomponent?.invoke(this)
}
if (widget != null) {
Box(
modifier = Modifier.padding(end = PrefsHorizontalPadding),
content = { widget() },
) )
} }
subcomponent?.invoke(this)
}
if (widget != null) {
Box(
modifier = Modifier.padding(end = PrefsHorizontalPadding),
content = { widget() },
)
} }
} }
} }

View File

@ -28,18 +28,18 @@ fun EditTextPreferenceWidget(
value: String, value: String,
onConfirm: suspend (String) -> Boolean, onConfirm: suspend (String) -> Boolean,
) { ) {
val (isDialogShown, showDialog) = remember { mutableStateOf(false) } var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget( TextPreferenceWidget(
title = title, title = title,
subtitle = subtitle?.format(value), subtitle = subtitle?.format(value),
icon = icon, icon = icon,
onPreferenceClick = { showDialog(true) }, onPreferenceClick = { isDialogShown = true },
) )
if (isDialogShown) { if (isDialogShown) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val onDismissRequest = { showDialog(false) } val onDismissRequest = { isDialogShown = false }
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(value)) mutableStateOf(TextFieldValue(value))
} }

View File

@ -12,8 +12,10 @@ import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -36,18 +38,18 @@ fun <T> ListPreferenceWidget(
entries: Map<out T, String>, entries: Map<out T, String>,
onValueChange: (T) -> Unit, onValueChange: (T) -> Unit,
) { ) {
val (isDialogShown, showDialog) = remember { mutableStateOf(false) } var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget( TextPreferenceWidget(
title = title, title = title,
subtitle = subtitle, subtitle = subtitle,
icon = icon, icon = icon,
onPreferenceClick = { showDialog(true) }, onPreferenceClick = { isDialogShown = true },
) )
if (isDialogShown) { if (isDialogShown) {
AlertDialog( AlertDialog(
onDismissRequest = { showDialog(false) }, onDismissRequest = { isDialogShown = false },
title = { Text(text = title) }, title = { Text(text = title) },
text = { text = {
Box { Box {
@ -61,7 +63,7 @@ fun <T> ListPreferenceWidget(
isSelected = isSelected, isSelected = isSelected,
onSelected = { onSelected = {
onValueChange(current.key!!) onValueChange(current.key!!)
showDialog(false) isDialogShown = false
}, },
) )
} }
@ -72,7 +74,7 @@ fun <T> ListPreferenceWidget(
} }
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { showDialog(false) }) { TextButton(onClick = { isDialogShown = false }) {
Text(text = stringResource(R.string.action_cancel)) Text(text = stringResource(R.string.action_cancel))
} }
}, },

View File

@ -11,8 +11,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -30,13 +32,13 @@ fun MultiSelectListPreferenceWidget(
values: Set<String>, values: Set<String>,
onValuesChange: (Set<String>) -> Unit, onValuesChange: (Set<String>) -> Unit,
) { ) {
val (isDialogShown, showDialog) = remember { mutableStateOf(false) } var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget( TextPreferenceWidget(
title = preference.title, title = preference.title,
subtitle = preference.subtitleProvider(values, preference.entries), subtitle = preference.subtitleProvider(values, preference.entries),
icon = preference.icon, icon = preference.icon,
onPreferenceClick = { showDialog(true) }, onPreferenceClick = { isDialogShown = true },
) )
if (isDialogShown) { if (isDialogShown) {
@ -46,7 +48,7 @@ fun MultiSelectListPreferenceWidget(
.toMutableStateList() .toMutableStateList()
} }
AlertDialog( AlertDialog(
onDismissRequest = { showDialog(false) }, onDismissRequest = { isDialogShown = false },
title = { Text(text = preference.title) }, title = { Text(text = preference.title) },
text = { text = {
LazyColumn { LazyColumn {
@ -91,14 +93,14 @@ fun MultiSelectListPreferenceWidget(
TextButton( TextButton(
onClick = { onClick = {
onValuesChange(selected.toMutableSet()) onValuesChange(selected.toMutableSet())
showDialog(false) isDialogShown = false
}, },
) { ) {
Text(text = stringResource(android.R.string.ok)) Text(text = stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDialog(false) }) { TextButton(onClick = { isDialogShown = false }) {
Text(text = stringResource(R.string.action_cancel)) Text(text = stringResource(R.string.action_cancel))
} }
}, },

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview
@Composable @Composable
fun SwitchPreferenceWidget( fun SwitchPreferenceWidget(
modifier: Modifier = Modifier,
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
icon: ImageVector? = null, icon: ImageVector? = null,
@ -21,6 +22,7 @@ fun SwitchPreferenceWidget(
onCheckedChanged: (Boolean) -> Unit, onCheckedChanged: (Boolean) -> Unit,
) { ) {
TextPreferenceWidget( TextPreferenceWidget(
modifier = modifier,
title = title, title = title,
subtitle = subtitle, subtitle = subtitle,
icon = icon, icon = icon,

View File

@ -2,11 +2,8 @@ package eu.kanade.presentation.updates
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Refresh
@ -23,17 +20,17 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadAction
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.MangaBottomActionMenu import eu.kanade.presentation.components.MangaBottomActionMenu
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SwipeRefresh import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.VerticalFastScroller
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
@ -124,7 +121,6 @@ private fun UpdateScreenContent(
onClickCover: (UpdatesItem) -> Unit, onClickCover: (UpdatesItem) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val updatesListState = rememberLazyListState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
@ -143,34 +139,26 @@ private fun UpdateScreenContent(
enabled = presenter.selectionMode.not(), enabled = presenter.selectionMode.not(),
indicatorPadding = contentPadding, indicatorPadding = contentPadding,
) { ) {
VerticalFastScroller( FastScrollLazyColumn(
listState = updatesListState, contentPadding = contentPadding,
topContentPadding = contentPadding.calculateTopPadding(),
endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
) { ) {
LazyColumn( if (presenter.lastUpdated > 0L) {
modifier = Modifier.fillMaxHeight(), updatesLastUpdatedItem(presenter.lastUpdated)
state = updatesListState,
contentPadding = contentPadding,
) {
if (presenter.lastUpdated > 0L) {
updatesLastUpdatedItem(presenter.lastUpdated)
}
updatesUiItems(
uiModels = presenter.uiModels,
selectionMode = presenter.selectionMode,
onUpdateSelected = presenter::toggleSelection,
onClickCover = onClickCover,
onClickUpdate = {
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
context.startActivity(intent)
},
onDownloadChapter = presenter::downloadChapters,
relativeTime = presenter.relativeTime,
dateFormat = presenter.dateFormat,
)
} }
updatesUiItems(
uiModels = presenter.uiModels,
selectionMode = presenter.selectionMode,
onUpdateSelected = presenter::toggleSelection,
onClickCover = onClickCover,
onClickUpdate = {
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
context.startActivity(intent)
},
onDownloadChapter = presenter::downloadChapters,
relativeTime = presenter.relativeTime,
dateFormat = presenter.dateFormat,
)
} }
} }
@ -255,24 +243,24 @@ private fun UpdatesBottomBar(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onBookmarkClicked = { onBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected, true) onMultiBookmarkClicked.invoke(selected, true)
}.takeIf { selected.any { !it.update.bookmark } }, }.takeIf { selected.fastAny { !it.update.bookmark } },
onRemoveBookmarkClicked = { onRemoveBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected, false) onMultiBookmarkClicked.invoke(selected, false)
}.takeIf { selected.all { it.update.bookmark } }, }.takeIf { selected.fastAll { it.update.bookmark } },
onMarkAsReadClicked = { onMarkAsReadClicked = {
onMultiMarkAsReadClicked(selected, true) onMultiMarkAsReadClicked(selected, true)
}.takeIf { selected.any { !it.update.read } }, }.takeIf { selected.fastAny { !it.update.read } },
onMarkAsUnreadClicked = { onMarkAsUnreadClicked = {
onMultiMarkAsReadClicked(selected, false) onMultiMarkAsReadClicked(selected, false)
}.takeIf { selected.any { it.update.read } }, }.takeIf { selected.fastAny { it.update.read } },
onDownloadClicked = { onDownloadClicked = {
onDownloadChapter(selected, ChapterDownloadAction.START) onDownloadChapter(selected, ChapterDownloadAction.START)
}.takeIf { }.takeIf {
selected.any { it.downloadStateProvider() != Download.State.DOWNLOADED } selected.fastAny { it.downloadStateProvider() != Download.State.DOWNLOADED }
}, },
onDeleteClicked = { onDeleteClicked = {
onMultiDeleteClicked(selected) onMultiDeleteClicked(selected)
}.takeIf { selected.any { it.downloadStateProvider() == Download.State.DOWNLOADED } }, }.takeIf { selected.fastAny { it.downloadStateProvider() == Download.State.DOWNLOADED } },
) )
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.presentation.updates package eu.kanade.presentation.updates
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -28,7 +27,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
@ -43,6 +41,7 @@ import eu.kanade.presentation.components.MangaCover
import eu.kanade.presentation.components.RelativeDateHeader import eu.kanade.presentation.components.RelativeDateHeader
import eu.kanade.presentation.util.ReadItemAlpha import eu.kanade.presentation.util.ReadItemAlpha
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
@ -135,6 +134,7 @@ fun LazyListScope.updatesUiItems(
onDownloadChapter = { onDownloadChapter = {
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it) if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
}, },
downloadIndicatorEnabled = selectionMode.not(),
downloadStateProvider = updatesItem.downloadStateProvider, downloadStateProvider = updatesItem.downloadStateProvider,
downloadProgressProvider = updatesItem.downloadProgressProvider, downloadProgressProvider = updatesItem.downloadProgressProvider,
) )
@ -153,13 +153,14 @@ fun UpdatesUiItem(
onClickCover: () -> Unit, onClickCover: () -> Unit,
onDownloadChapter: (ChapterDownloadAction) -> Unit, onDownloadChapter: (ChapterDownloadAction) -> Unit,
// Download Indicator // Download Indicator
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State, downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
Row( Row(
modifier = modifier modifier = modifier
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent) .selectedBackground(selected)
.combinedClickable( .combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = { onLongClick = {
@ -225,6 +226,7 @@ fun UpdatesUiItem(
} }
} }
ChapterDownloadIndicator( ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp), modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider, downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider, downloadProgressProvider = downloadProgressProvider,

View File

@ -12,3 +12,4 @@ val verticalPadding = vertical
val topPaddingValues = PaddingValues(top = vertical) val topPaddingValues = PaddingValues(top = vertical)
const val ReadItemAlpha = .38f const val ReadItemAlpha = .38f
const val SecondaryItemAlpha = .78f

View File

@ -29,7 +29,7 @@ fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed {
} }
} }
fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(.78f) fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)
fun Modifier.clickableNoIndication( fun Modifier.clickableNoIndication(
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,

View File

@ -244,6 +244,10 @@ class Downloader(
* @param autoStart whether to start the downloader after enqueing the chapters. * @param autoStart whether to start the downloader after enqueing the chapters.
*/ */
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO { fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO {
if (chapters.isEmpty()) {
return@launchIO
}
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO
val wasEmpty = queue.isEmpty() val wasEmpty = queue.isEmpty()
// Called in background thread, the operation can be slow with SAF. // Called in background thread, the operation can be slow with SAF.

View File

@ -415,8 +415,7 @@ class LibraryUpdateService(
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
// We don't want to start downloading while the library is updating, because websites // We don't want to start downloading while the library is updating, because websites
// may don't like it and they could ban the user. // may don't like it and they could ban the user.
val dbChapters = chapters.map { it.toDbChapter() } downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() }, false)
downloadManager.downloadChapters(manga, dbChapters, false)
} }
/** /**

View File

@ -271,11 +271,9 @@ class NotificationReceiver : BroadcastReceiver() {
*/ */
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) { private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
launchIO { launchIO {
val manga = getManga.await(mangaId) val manga = getManga.await(mangaId) ?: return@launchIO
val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() } val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() }
if (manga != null && chapters.isNotEmpty()) { downloadManager.downloadChapters(manga, chapters)
downloadManager.downloadChapters(manga, chapters)
}
} }
} }

View File

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.getInstallerPackageName import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -38,7 +38,7 @@ class AppUpdateChecker {
// Check if latest version is different from current version // Check if latest version is different from current version
if (isNewVersion(it.version)) { if (isNewVersion(it.version)) {
if (context.getInstallerPackageName() == "org.fdroid.fdroid") { if (context.isInstalledFromFDroid()) {
AppUpdateResult.NewUpdateFdroidInstallation AppUpdateResult.NewUpdateFdroidInstallation
} else { } else {
AppUpdateResult.NewUpdate(it) AppUpdateResult.NewUpdate(it)

View File

@ -32,13 +32,7 @@ class SourceManager(
private val scope = CoroutineScope(Job() + Dispatchers.IO) private val scope = CoroutineScope(Job() + Dispatchers.IO)
private var sourcesMap = ConcurrentHashMap<Long, Source>() private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap<Long, Source>())
set(value) {
field = value
sourcesMapFlow.value = field
}
private val sourcesMapFlow = MutableStateFlow(sourcesMap)
private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>() private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>()
@ -56,7 +50,7 @@ class SourceManager(
registerStubSource(it.toSourceData()) registerStubSource(it.toSourceData())
} }
} }
sourcesMap = mutableMap sourcesMapFlow.value = mutableMap
} }
} }
@ -72,18 +66,18 @@ class SourceManager(
} }
fun get(sourceKey: Long): Source? { fun get(sourceKey: Long): Source? {
return sourcesMap[sourceKey] return sourcesMapFlow.value[sourceKey]
} }
fun getOrStub(sourceKey: Long): Source { fun getOrStub(sourceKey: Long): Source {
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
runBlocking { createStubSource(sourceKey) } runBlocking { createStubSource(sourceKey) }
} }
} }
fun getOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>() fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>() fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<CatalogueSource>()
fun getStubSources(): List<StubSource> { fun getStubSources(): List<StubSource> {
val onlineSourceIds = getOnlineSources().map { it.id } val onlineSourceIds = getOnlineSources().map { it.id }

View File

@ -45,7 +45,6 @@ class BrowseController : FullComposeController<BrowsePresenter>, RootController
startIndex = 1.takeIf { toExtensions }, startIndex = 1.takeIf { toExtensions },
searchQuery = query, searchQuery = query,
onChangeSearchQuery = { presenter.extensionsPresenter.search(it) }, onChangeSearchQuery = { presenter.extensionsPresenter.search(it) },
placeholderRes = R.string.action_search_hint,
incognitoMode = presenter.isIncognitoMode, incognitoMode = presenter.isIncognitoMode,
downloadedOnlyMode = presenter.isDownloadOnly, downloadedOnlyMode = presenter.isDownloadOnly,
) )

View File

@ -13,7 +13,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -36,7 +35,6 @@ class ExtensionFilterPresenter(
logcat(LogPriority.ERROR, exception) logcat(LogPriority.ERROR, exception)
_events.send(Event.FailedFetchingLanguages) _events.send(Event.FailedFetchingLanguages)
} }
.stateIn(presenterScope)
.collectLatest(::collectLatestSourceLangMap) .collectLatest(::collectLatestSourceLangMap)
} }
} }

View File

@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -116,7 +117,7 @@ class ExtensionsPresenter(
items items
} }
.stateIn(presenterScope) .onStart { delay(500) } // Defer to avoid crashing on initial render
.collectLatest { .collectLatest {
state.isLoading = false state.isLoading = false
state.items = it state.items = it

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.browse.extension package eu.kanade.tachiyomi.ui.browse.extension
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Translate import androidx.compose.material.icons.outlined.Translate
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -21,13 +20,8 @@ fun extensionsTab(
) = TabContent( ) = TabContent(
titleRes = R.string.label_extensions, titleRes = R.string.label_extensions,
badgeNumber = presenter.updates.takeIf { it > 0 }, badgeNumber = presenter.updates.takeIf { it > 0 },
searchEnabled = true,
actions = listOf( actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_search),
icon = Icons.Outlined.Search,
onClick = { presenter.search("") },
),
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_filter), title = stringResource(R.string.action_filter),
icon = Icons.Outlined.Translate, icon = Icons.Outlined.Translate,

View File

@ -15,7 +15,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -40,7 +39,6 @@ class SourcesFilterPresenter(
logcat(LogPriority.ERROR, exception) logcat(LogPriority.ERROR, exception)
_events.send(Event.FailedFetchingLanguages) _events.send(Event.FailedFetchingLanguages)
} }
.stateIn(presenterScope)
.collectLatest(::collectLatestSourceLangMap) .collectLatest(::collectLatestSourceLangMap)
} }
} }

View File

@ -14,10 +14,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -43,7 +44,7 @@ class SourcesPresenter(
logcat(LogPriority.ERROR, exception) logcat(LogPriority.ERROR, exception)
_events.send(Event.FailedFetchingSources) _events.send(Event.FailedFetchingSources)
} }
.stateIn(presenterScope) .onStart { delay(500) } // Defer to avoid crashing on initial render
.collectLatest(::collectLatestSources) .collectLatest(::collectLatestSources)
} }
} }

View File

@ -119,11 +119,7 @@ open class BrowseSourceController(bundle: Bundle) :
private fun navigateUp() { private fun navigateUp() {
when { when {
presenter.searchQuery != null -> presenter.searchQuery = null !presenter.isUserQuery && presenter.searchQuery != null -> presenter.searchQuery = null
presenter.isUserQuery -> {
val (_, filters) = presenter.currentFilter as BrowseSourcePresenter.Filter.UserInput
presenter.search(query = "", filters = filters)
}
else -> router.popCurrentController() else -> router.popCurrentController()
} }
} }

View File

@ -131,7 +131,7 @@ open class BrowseSourcePresenter(
.map { .map {
it.map { sManga -> it.map { sManga ->
withIOContext { withIOContext {
networkToLocalManga.await(sManga.toDomainManga(), sourceId) networkToLocalManga.await(sManga.toDomainManga(sourceId))
} }
} }
} }
@ -154,15 +154,15 @@ open class BrowseSourcePresenter(
} }
fun reset() { fun reset() {
state.filters = source!!.getFilterList() val source = source ?: return
if (currentFilter !is Filter.UserInput) return state.filters = source.getFilterList()
state.currentFilter = (currentFilter as Filter.UserInput).copy(filters = state.filters)
} }
fun search(query: String? = null, filters: FilterList? = null) { fun search(query: String? = null, filters: FilterList? = null) {
Filter.valueOf(query ?: "").let { Filter.valueOf(query ?: "").let {
if (it !is Filter.UserInput) { if (it !is Filter.UserInput) {
state.currentFilter = it state.currentFilter = it
state.searchQuery = null
return return
} }
} }

View File

@ -260,6 +260,6 @@ open class GlobalSearchPresenter(
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): DomainManga { protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): DomainManga {
return networkToLocalManga.await(sManga.toDomainManga(), sourceId) return networkToLocalManga.await(sManga.toDomainManga(sourceId))
} }
} }

View File

@ -14,11 +14,9 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Pause import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@ -52,6 +50,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.components.Pill import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -61,7 +60,6 @@ import eu.kanade.tachiyomi.databinding.DownloadListBinding
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import me.saket.cascade.CascadeDropdownMenu
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -147,69 +145,69 @@ class DownloadController :
navigateUp = router::popCurrentController, navigateUp = router::popCurrentController,
actions = { actions = {
if (downloadList.isNotEmpty()) { if (downloadList.isNotEmpty()) {
val (expanded, onExpanded) = remember { mutableStateOf(false) } OverflowMenu { closeMenu ->
Box { DropdownMenuItem(
IconButton(onClick = { onExpanded(!expanded) }) { text = { Text(text = stringResource(R.string.action_reorganize_by)) },
Icon( children = {
imageVector = Icons.Outlined.MoreVert, DropdownMenuItem(
contentDescription = stringResource(R.string.abc_action_menu_overflow_description), text = { Text(text = stringResource(R.string.action_order_by_upload_date)) },
) children = {
} DropdownMenuItem(
CascadeDropdownMenu( text = { Text(text = stringResource(R.string.action_newest)) },
expanded = expanded, onClick = {
onDismissRequest = { onExpanded(false) }, reorderQueue(
) { { it.download.chapter.date_upload },
DropdownMenuItem( true,
text = { Text(text = stringResource(R.string.action_reorganize_by)) }, )
children = { closeMenu()
DropdownMenuItem( },
text = { Text(text = stringResource(R.string.action_order_by_upload_date)) }, )
children = { DropdownMenuItem(
DropdownMenuItem( text = { Text(text = stringResource(R.string.action_oldest)) },
text = { Text(text = stringResource(R.string.action_newest)) }, onClick = {
onClick = { reorderQueue(
reorderQueue({ it.download.chapter.date_upload }, true) { it.download.chapter.date_upload },
onExpanded(false) false,
}, )
) closeMenu()
DropdownMenuItem( },
text = { Text(text = stringResource(R.string.action_oldest)) }, )
onClick = { },
reorderQueue({ it.download.chapter.date_upload }, false) )
onExpanded(false) DropdownMenuItem(
}, text = { Text(text = stringResource(R.string.action_order_by_chapter_number)) },
) children = {
}, DropdownMenuItem(
) text = { Text(text = stringResource(R.string.action_asc)) },
DropdownMenuItem( onClick = {
text = { Text(text = stringResource(R.string.action_order_by_chapter_number)) }, reorderQueue(
children = { { it.download.chapter.chapter_number },
DropdownMenuItem( false,
text = { Text(text = stringResource(R.string.action_asc)) }, )
onClick = { closeMenu()
reorderQueue({ it.download.chapter.chapter_number }, false) },
onExpanded(false) )
}, DropdownMenuItem(
) text = { Text(text = stringResource(R.string.action_desc)) },
DropdownMenuItem( onClick = {
text = { Text(text = stringResource(R.string.action_desc)) }, reorderQueue(
onClick = { { it.download.chapter.chapter_number },
reorderQueue({ it.download.chapter.chapter_number }, true) true,
onExpanded(false) )
}, closeMenu()
) },
}, )
) },
}, )
) },
DropdownMenuItem( )
text = { Text(text = stringResource(R.string.action_cancel_all)) }, DropdownMenuItem(
onClick = { text = { Text(text = stringResource(R.string.action_cancel_all)) },
presenter.clearQueue(context) onClick = {
onExpanded(false) presenter.clearQueue(context)
}, closeMenu()
) },
} )
} }
} }
}, },

View File

@ -15,6 +15,8 @@ import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.DeleteLibraryMangaDialog import eu.kanade.presentation.components.DeleteLibraryMangaDialog
import eu.kanade.presentation.library.LibraryScreen import eu.kanade.presentation.library.LibraryScreen
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
@ -43,6 +45,8 @@ class LibraryController(
@Composable @Composable
override fun ComposeContent() { override fun ComposeContent() {
val context = LocalContext.current val context = LocalContext.current
val getMangaForCategory = presenter.getMangaForCategory(page = presenter.activeCategory)
LibraryScreen( LibraryScreen(
presenter = presenter, presenter = presenter,
onMangaClicked = ::openManga, onMangaClicked = ::openManga,
@ -52,7 +56,7 @@ class LibraryController(
onChangeCategoryClicked = ::showMangaCategoriesDialog, onChangeCategoryClicked = ::showMangaCategoriesDialog,
onMarkAsReadClicked = { markReadStatus(true) }, onMarkAsReadClicked = { markReadStatus(true) },
onMarkAsUnreadClicked = { markReadStatus(false) }, onMarkAsUnreadClicked = { markReadStatus(false) },
onDownloadClicked = ::downloadUnreadChapters, onDownloadClicked = ::runDownloadChapterAction,
onDeleteClicked = ::showDeleteMangaDialog, onDeleteClicked = ::showDeleteMangaDialog,
onClickFilter = ::showSettingsSheet, onClickFilter = ::showSettingsSheet,
onClickRefresh = { onClickRefresh = {
@ -60,6 +64,14 @@ class LibraryController(
context.toast(if (started) R.string.updating_category else R.string.update_already_running) context.toast(if (started) R.string.updating_category else R.string.update_already_running)
started started
}, },
onClickOpenRandomManga = {
val items = getMangaForCategory.map { it.libraryManga.manga.id }
if (getMangaForCategory.isNotEmpty()) {
openManga(items.random())
} else {
context.toast(R.string.information_no_entries_found)
}
},
onClickInvertSelection = { presenter.invertSelection(presenter.activeCategory) }, onClickInvertSelection = { presenter.invertSelection(presenter.activeCategory) },
onClickSelectAll = { presenter.selectAll(presenter.activeCategory) }, onClickSelectAll = { presenter.selectAll(presenter.activeCategory) },
onClickUnselectAll = ::clearSelection, onClickUnselectAll = ::clearSelection,
@ -91,6 +103,16 @@ class LibraryController(
}, },
) )
} }
is LibraryPresenter.Dialog.DownloadCustomAmount -> {
DownloadCustomAmountDialog(
maxAmount = dialog.max,
onDismissRequest = onDismissRequest,
onConfirm = { amount ->
presenter.downloadUnreadChapters(dialog.manga, amount)
presenter.clearSelection()
},
)
}
null -> {} null -> {}
} }
@ -208,9 +230,22 @@ class LibraryController(
} }
} }
private fun downloadUnreadChapters() { private fun runDownloadChapterAction(action: DownloadAction) {
val mangaList = presenter.selection.toList() val mangas = presenter.selection.map { it.manga }.toList()
presenter.downloadUnreadChapters(mangaList.map { it.manga }) when (action) {
DownloadAction.NEXT_1_CHAPTER -> presenter.downloadUnreadChapters(mangas, 1)
DownloadAction.NEXT_5_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 5)
DownloadAction.NEXT_10_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 10)
DownloadAction.UNREAD_CHAPTERS -> presenter.downloadUnreadChapters(mangas, null)
DownloadAction.CUSTOM -> {
presenter.dialog = LibraryPresenter.Dialog.DownloadCustomAmount(
mangas,
presenter.selection.maxOf { it.unreadCount }.toInt(),
)
return
}
else -> {}
}
presenter.clearSelection() presenter.clearSelection()
} }

View File

@ -17,9 +17,9 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.library.model.LibrarySort import eu.kanade.domain.library.model.LibrarySort
import eu.kanade.domain.library.model.sort import eu.kanade.domain.library.model.sort
@ -78,7 +78,7 @@ class LibraryPresenter(
private val getLibraryManga: GetLibraryManga = Injekt.get(), private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracksPerManga: GetTracksPerManga = Injekt.get(), private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
private val setReadStatus: SetReadStatus = Injekt.get(), private val setReadStatus: SetReadStatus = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(),
@ -402,18 +402,27 @@ class LibraryPresenter(
} }
/** /**
* Queues all unread chapters from the given list of manga. * Queues the amount specified of unread chapters from the list of mangas given.
* *
* @param mangas the list of manga. * @param mangas the list of manga.
* @param amount the amount to queue or null to queue all
*/ */
fun downloadUnreadChapters(mangas: List<Manga>) { fun downloadUnreadChapters(mangas: List<Manga>, amount: Int?) {
presenterScope.launchNonCancellable { presenterScope.launchNonCancellable {
mangas.forEach { manga -> mangas.forEach { manga ->
val chapters = getChapterByMangaId.await(manga.id) val chapters = getNextUnreadChapters.await(manga.id)
.filter { !it.read } .filterNot { chapter ->
.map { it.toDbChapter() } downloadManager.queue.any { chapter.id == it.chapter.id } ||
downloadManager.isChapterDownloaded(
chapter.name,
chapter.scanlator,
manga.title,
manga.source,
)
}
.let { if (amount != null) it.take(amount) else it }
downloadManager.downloadChapters(manga, chapters) downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() })
} }
} }
} }
@ -526,7 +535,7 @@ class LibraryPresenter(
@Composable @Composable
fun getMangaForCategory(page: Int): List<LibraryItem> { fun getMangaForCategory(page: Int): List<LibraryItem> {
val unfiltered = remember(categories, loadedManga) { val unfiltered = remember(categories, loadedManga, page) {
val categoryId = categories.getOrNull(page)?.id ?: -1 val categoryId = categories.getOrNull(page)?.id ?: -1
loadedManga[categoryId] ?: emptyList() loadedManga[categoryId] ?: emptyList()
} }
@ -604,5 +613,6 @@ class LibraryPresenter(
sealed class Dialog { sealed class Dialog {
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog() data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
data class DeleteManga(val manga: List<Manga>) : Dialog() data class DeleteManga(val manga: List<Manga>) : Dialog()
data class DownloadCustomAmount(val manga: List<Manga>, val max: Int) : Dialog()
} }
} }

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.category.CategoryController import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.download.DownloadController import eu.kanade.tachiyomi.ui.download.DownloadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
class MoreController : class MoreController :
FullComposeController<MorePresenter>(), FullComposeController<MorePresenter>(),
@ -19,6 +20,7 @@ class MoreController :
override fun ComposeContent() { override fun ComposeContent() {
MoreScreen( MoreScreen(
presenter = presenter, presenter = presenter,
isFDroid = activity?.isInstalledFromFDroid() ?: false,
onClickDownloadQueue = { router.pushController(DownloadController()) }, onClickDownloadQueue = { router.pushController(DownloadController()) },
onClickCategories = { router.pushController(CategoryController()) }, onClickCategories = { router.pushController(CategoryController()) },
onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) }, onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) },

View File

@ -11,6 +11,7 @@ import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.download.service.DownloadPreferences
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
import eu.kanade.domain.history.interactor.UpsertHistory import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
@ -22,7 +23,6 @@ import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
@ -59,7 +59,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toInt
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -74,7 +73,6 @@ import uy.kohesive.injekt.injectLazy
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
/** /**
* Presenter used by the activity to perform background operations. * Presenter used by the activity to perform background operations.
@ -90,6 +88,7 @@ class ReaderPresenter(
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(), private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(), private val upsertHistory: UpsertHistory = Injekt.get(),
@ -393,7 +392,13 @@ class ReaderPresenter(
if (chapter.pageLoader is HttpPageLoader) { if (chapter.pageLoader is HttpPageLoader) {
val manga = manga ?: return val manga = manga ?: return
val dbChapter = chapter.chapter val dbChapter = chapter.chapter
val isDownloaded = downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source, skipCache = true) val isDownloaded = downloadManager.isChapterDownloaded(
dbChapter.name,
dbChapter.scanlator,
manga.title,
manga.source,
skipCache = true,
)
if (isDownloaded) { if (isDownloaded) {
chapter.state = ReaderChapter.State.Wait chapter.state = ReaderChapter.State.Wait
} }
@ -406,7 +411,6 @@ class ReaderPresenter(
logcat { "Preloading ${chapter.chapter.url}" } logcat { "Preloading ${chapter.chapter.url}" }
val loader = loader ?: return val loader = loader ?: return
loader.loadChapter(chapter) loader.loadChapter(chapter)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// Update current chapters whenever a chapter is preloaded // Update current chapters whenever a chapter is preloaded
@ -447,7 +451,7 @@ class ReaderPresenter(
loadNewChapter(selectedChapter) loadNewChapter(selectedChapter)
} }
val pages = page.chapter.pages ?: return val pages = page.chapter.pages ?: return
val inDownloadRange = page.number.toDouble() / pages.size > 0.2 val inDownloadRange = page.number.toDouble() / pages.size > 0.25
if (inDownloadRange) { if (inDownloadRange) {
downloadNextChapters() downloadNextChapters()
} }
@ -455,45 +459,31 @@ class ReaderPresenter(
private fun downloadNextChapters() { private fun downloadNextChapters() {
val manga = manga ?: return val manga = manga ?: return
val amount = downloadPreferences.autoDownloadWhileReading().get()
if (amount == 0 || !manga.favorite) return
// Only download ahead if current + next chapter is already downloaded too to avoid jank
if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
val nextChapter = viewerChaptersRelay.value?.nextChapter?.chapter ?: return val nextChapter = viewerChaptersRelay.value?.nextChapter?.chapter ?: return
val chaptersNumberToDownload = downloadPreferences.autoDownloadWhileReading().get()
if (chaptersNumberToDownload == 0 || !manga.favorite) return presenterScope.launchIO {
val isNextChapterDownloadedOrQueued = downloadManager.isChapterDownloaded( val isNextChapterDownloaded = downloadManager.isChapterDownloaded(
nextChapter.name, nextChapter.name,
nextChapter.scanlator, nextChapter.scanlator,
manga.title, manga.title,
manga.source, manga.source,
skipCache = true, )
) || downloadManager.getChapterDownloadOrNull(nextChapter) != null if (!isNextChapterDownloaded) return@launchIO
if (isNextChapterDownloadedOrQueued) {
downloadAutoNextChapters(chaptersNumberToDownload, nextChapter.id, nextChapter.read) val chaptersToDownload = getNextUnreadChapters.await(manga.id!!, nextChapter.id!!)
.take(amount)
downloadManager.downloadChapters(
manga.toDomainManga()!!,
chaptersToDownload.map { it.toDbChapter() },
)
} }
} }
private fun downloadAutoNextChapters(choice: Int, nextChapterId: Long?, isNextChapterRead: Boolean) {
val chaptersToDownload = getNextUnreadChaptersSorted(nextChapterId).take(choice - 1 + isNextChapterRead.toInt())
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
}
private fun getNextUnreadChaptersSorted(nextChapterId: Long?): List<DbChapter> {
return chapterList.map { it.chapter.toDomainChapter()!! }
.filter { !it.read || it.id == nextChapterId }
.sortedWith(getChapterSort(manga?.toDomainManga()!!, false))
.map { it.toDbChapter() }
.takeLastWhile { it.id != nextChapterId }
}
/**
* Downloads the given list of chapters with the manager.
* @param chapters the list of chapters to download.
*/
private fun downloadChapters(chapters: List<DbChapter>) {
downloadManager.downloadChapters(manga?.toDomainManga()!!, chapters)
}
/** /**
* Removes [currentChapter] from download queue * Removes [currentChapter] from download queue
* if setting is enabled and [currentChapter] is queued for download * if setting is enabled and [currentChapter] is queued for download

View File

@ -9,10 +9,10 @@ enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val str
DEFAULT(0, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.label_default, R.drawable.ic_screen_rotation_24dp, 0x00000000), DEFAULT(0, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.label_default, R.drawable.ic_screen_rotation_24dp, 0x00000000),
FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, R.drawable.ic_screen_rotation_24dp, 0x00000008), FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, R.drawable.ic_screen_rotation_24dp, 0x00000008),
PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000010), PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000010),
REVERSE_PORTRAIT(6, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, R.string.rotation_reverse_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000030),
LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, R.drawable.ic_stay_current_landscape_24dp, 0x00000018), LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, R.drawable.ic_stay_current_landscape_24dp, 0x00000018),
LOCKED_PORTRAIT(4, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp, 0x00000020), LOCKED_PORTRAIT(4, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp, 0x00000020),
LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp, 0x00000028), LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp, 0x00000028),
REVERSE_PORTRAIT(6, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, R.string.rotation_reverse_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000030),
; ;
companion object { companion object {

View File

@ -11,7 +11,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.history.interactor.DeleteAllHistory import eu.kanade.domain.history.interactor.DeleteAllHistory
import eu.kanade.domain.history.interactor.GetHistory import eu.kanade.domain.history.interactor.GetHistory
import eu.kanade.domain.history.interactor.GetNextChapter import eu.kanade.domain.history.interactor.GetNextUnreadChapters
import eu.kanade.domain.history.interactor.RemoveHistoryById import eu.kanade.domain.history.interactor.RemoveHistoryById
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
@ -37,7 +37,7 @@ import java.util.Date
class HistoryPresenter( class HistoryPresenter(
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl, private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
private val getHistory: GetHistory = Injekt.get(), private val getHistory: GetHistory = Injekt.get(),
private val getNextChapter: GetNextChapter = Injekt.get(), private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
private val deleteAllHistory: DeleteAllHistory = Injekt.get(), private val deleteAllHistory: DeleteAllHistory = Injekt.get(),
private val removeHistoryById: RemoveHistoryById = Injekt.get(), private val removeHistoryById: RemoveHistoryById = Injekt.get(),
private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(), private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
@ -94,7 +94,7 @@ class HistoryPresenter(
fun getNextChapterForManga(mangaId: Long, chapterId: Long) { fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
presenterScope.launchIO { presenterScope.launchIO {
val chapter = getNextChapter.await(mangaId, chapterId) val chapter = getNextUnreadChapters.await(mangaId, chapterId).firstOrNull()
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound) _events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
} }
} }
@ -111,7 +111,7 @@ class HistoryPresenter(
fun resumeLastChapterRead() { fun resumeLastChapterRead() {
presenterScope.launchIO { presenterScope.launchIO {
val chapter = getNextChapter.await() val chapter = getNextUnreadChapters.await()
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound) _events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
} }
} }

View File

@ -29,13 +29,14 @@ import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -87,11 +88,11 @@ class UpdatesPresenter(
getUpdates.subscribe(calendar).distinctUntilChanged(), getUpdates.subscribe(calendar).distinctUntilChanged(),
downloadCache.changes, downloadCache.changes,
) { updates, _ -> updates } ) { updates, _ -> updates }
.onStart { delay(500) } // Defer to avoid crashing on initial render
.catch { .catch {
logcat(LogPriority.ERROR, it) logcat(LogPriority.ERROR, it)
_events.send(Event.InternalError) _events.send(Event.InternalError)
} }
.stateIn(presenterScope)
.collectLatest { updates -> .collectLatest { updates ->
state.items = updates.toUpdateItems() state.items = updates.toUpdateItems()
state.isLoading = false state.isLoading = false

View File

@ -1,5 +1,3 @@
package eu.kanade.tachiyomi.util.system package eu.kanade.tachiyomi.util.system
fun Boolean.toInt() = if (this) 1 else 0
fun Boolean.toLong() = if (this) 1L else 0L fun Boolean.toLong() = if (this) 1L else 0L

View File

@ -39,6 +39,7 @@ import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.TabletUiMode import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@ -393,8 +394,8 @@ fun Context.isPackageInstalled(packageName: String): Boolean {
} }
} }
fun Context.getInstallerPackageName(): String? { fun Context.isInstalledFromFDroid(): Boolean {
return try { val installerPackageName = try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
packageManager.getInstallSourceInfo(packageName).installingPackageName packageManager.getInstallSourceInfo(packageName).installingPackageName
} else { } else {
@ -404,6 +405,10 @@ fun Context.getInstallerPackageName(): String? {
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
return installerPackageName == "org.fdroid.fdroid" ||
// F-Droid builds typically disable the updater
(!BuildConfig.INCLUDE_UPDATER && !isDevFlavor)
} }
fun Context.getApplicationIcon(pkgName: String): Drawable? { fun Context.getApplicationIcon(pkgName: String): Drawable? {

View File

@ -64,6 +64,7 @@
<item>@string/rotation_landscape</item> <item>@string/rotation_landscape</item>
<item>@string/rotation_force_portrait</item> <item>@string/rotation_force_portrait</item>
<item>@string/rotation_force_landscape</item> <item>@string/rotation_force_landscape</item>
<item>@string/rotation_reverse_portrait</item>
</string-array> </string-array>
<string-array name="color_filter_modes"> <string-array name="color_filter_modes">

View File

@ -32,10 +32,12 @@ SELECT *
FROM mangas FROM mangas
WHERE _id = :id; WHERE _id = :id;
-- TODO: this should ideally never really have more than 1 result
getMangaByUrlAndSource: getMangaByUrlAndSource:
SELECT * SELECT *
FROM mangas FROM mangas
WHERE url = :url AND source = :source; WHERE url = :url AND source = :source
LIMIT 1;
getFavorites: getFavorites:
SELECT * SELECT *

View File

@ -19,6 +19,4 @@ material-icons = { module = "androidx.compose.material:material-icons-extended"
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" } accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" }
accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" } accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" } accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-pager-core = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }

View File

@ -1,18 +1,19 @@
[versions] [versions]
kotlin_version = "1.7.20" kotlin_version = "1.7.20"
coroutines_version = "1.6.4" serialization_version = "1.4.0"
serialization_version = "1.4.1"
xml_serialization_version = "0.84.3" xml_serialization_version = "0.84.3"
[libraries] [libraries]
reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" } reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" }
gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" } coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.6.4" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } # TODO: 1.4.1 introduces an issue with cached serializers; see https://github.com/Kotlin/kotlinx.serialization/issues/2065
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization_version" } serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.4.0" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version = "1.4.0" }
serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" } serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" }
serialization-gradle = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" } serialization-gradle = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" }
serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xml_serialization_version" } serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xml_serialization_version" }

View File

@ -8,7 +8,7 @@ flowbinding_version = "1.2.0"
shizuku_version = "12.2.0" shizuku_version = "12.2.0"
sqldelight = "1.5.4" sqldelight = "1.5.4"
leakcanary = "2.9.1" leakcanary = "2.9.1"
voyager = "1.0.0-rc05" voyager = "1.0.0-rc06"
[libraries] [libraries]
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"

View File

@ -661,7 +661,7 @@
<string name="notification_update_error">%1$d আপডেট(গুলি) ব্যর্থ হয়েছে</string> <string name="notification_update_error">%1$d আপডেট(গুলি) ব্যর্থ হয়েছে</string>
<string name="publishing_finished">সম্পূর্ণ প্রকাশিত</string> <string name="publishing_finished">সম্পূর্ণ প্রকাশিত</string>
<string name="action_filter_started">শুরু করা হয়েছে</string> <string name="action_filter_started">শুরু করা হয়েছে</string>
<string name="action_sort_last_manga_update">সর্বশেষ মাঙ্গা হালনাগাদ</string> <string name="action_sort_last_manga_update">সর্বশেষ হালনাগাদ চেক</string>
<string name="delete_category_confirmation">আপনি কি \"%s\" বিভাগটি মুছে ফেলতে চান\?</string> <string name="delete_category_confirmation">আপনি কি \"%s\" বিভাগটি মুছে ফেলতে চান\?</string>
<string name="appwidget_updates_description">সম্প্রতি আপনার হালনাগাদকৃত মাঙ্গা দেখুন</string> <string name="appwidget_updates_description">সম্প্রতি আপনার হালনাগাদকৃত মাঙ্গা দেখুন</string>
<string name="are_you_sure">আপনি কি নিশ্চিত\?</string> <string name="are_you_sure">আপনি কি নিশ্চিত\?</string>

View File

@ -2,7 +2,7 @@
<resources> <resources>
<string name="name">Nom</string> <string name="name">Nom</string>
<string name="categories">Categories</string> <string name="categories">Categories</string>
<string name="manga">Manga</string> <string name="manga">Mangues</string>
<string name="chapters">Capítols</string> <string name="chapters">Capítols</string>
<string name="track">En seguiment</string> <string name="track">En seguiment</string>
<string name="history">Historial</string> <string name="history">Historial</string>
@ -69,7 +69,7 @@
<string name="pref_category_general">General</string> <string name="pref_category_general">General</string>
<string name="pref_category_reader">Lector</string> <string name="pref_category_reader">Lector</string>
<string name="pref_category_downloads">Baixades</string> <string name="pref_category_downloads">Baixades</string>
<string name="pref_category_tracking">En seguiment</string> <string name="pref_category_tracking">Seguiment</string>
<string name="pref_category_advanced">Avançat</string> <string name="pref_category_advanced">Avançat</string>
<string name="pref_category_about">Quant a</string> <string name="pref_category_about">Quant a</string>
<string name="pref_library_columns">Elements per fila</string> <string name="pref_library_columns">Elements per fila</string>
@ -85,7 +85,7 @@
<string name="all">Tot</string> <string name="all">Tot</string>
<string name="pref_library_update_restriction">Restriccions del dispositiu per a les actualitzacions automàtiques</string> <string name="pref_library_update_restriction">Restriccions del dispositiu per a les actualitzacions automàtiques</string>
<string name="charging">Quan s\'estigui carregant</string> <string name="charging">Quan s\'estigui carregant</string>
<string name="pref_update_only_non_completed">Amb l\'estat «Completada»</string> <string name="pref_update_only_non_completed">Amb l\'estat «Completat»</string>
<string name="pref_auto_update_manga_sync">Actualitza el progrés després de llegir</string> <string name="pref_auto_update_manga_sync">Actualitza el progrés després de llegir</string>
<string name="default_category">Categoria per defecte</string> <string name="default_category">Categoria per defecte</string>
<string name="default_category_summary">Demana-ho sempre</string> <string name="default_category_summary">Demana-ho sempre</string>
@ -94,7 +94,7 @@
<string name="ext_pending">Pendent</string> <string name="ext_pending">Pendent</string>
<string name="ext_downloading">S\'està baixant</string> <string name="ext_downloading">S\'està baixant</string>
<string name="ext_installing">S\'està instal·lant</string> <string name="ext_installing">S\'està instal·lant</string>
<string name="ext_installed">Instal·lada</string> <string name="ext_installed">Instal·lades</string>
<string name="ext_trust">Confia-hi</string> <string name="ext_trust">Confia-hi</string>
<string name="ext_untrusted">No és de confiança</string> <string name="ext_untrusted">No és de confiança</string>
<string name="ext_uninstall">Desinstal·la</string> <string name="ext_uninstall">Desinstal·la</string>
@ -179,8 +179,8 @@
<string name="pref_clear_cookies">Esborra les galetes</string> <string name="pref_clear_cookies">Esborra les galetes</string>
<string name="cookies_cleared">S\'han esborrat les galetes</string> <string name="cookies_cleared">S\'han esborrat les galetes</string>
<string name="pref_clear_database">Buida la base de dades</string> <string name="pref_clear_database">Buida la base de dades</string>
<string name="pref_clear_database_summary">Suprimeix l\'historial del manga que no sigui a la biblioteca</string> <string name="pref_clear_database_summary">Suprimeix l\'historial dels elements que no siguin a la biblioteca</string>
<string name="clear_database_confirmation">N\'esteu segur\? Es perdrà el progrés i els capítols llegits del manga que no sigui a la biblioteca</string> <string name="clear_database_confirmation">N\'esteu segur\? Es perdrà el progrés i els capítols llegits dels elements que no siguin a la biblioteca</string>
<string name="clear_database_completed">S\'han suprimit les entrades</string> <string name="clear_database_completed">S\'han suprimit les entrades</string>
<string name="pref_refresh_library_tracking">Refresca el seguiment</string> <string name="pref_refresh_library_tracking">Refresca el seguiment</string>
<string name="pref_refresh_library_tracking_summary">Actualitza l\'estat, la puntuació i el darrer capítol llegit dels serveis de seguiment</string> <string name="pref_refresh_library_tracking_summary">Actualitza l\'estat, la puntuació i el darrer capítol llegit dels serveis de seguiment</string>
@ -244,8 +244,8 @@
<string name="error_category_exists">Ja hi ha una categoria amb aquest nom!</string> <string name="error_category_exists">Ja hi ha una categoria amb aquest nom!</string>
<string name="snack_categories_deleted">S\'han suprimit les categories</string> <string name="snack_categories_deleted">S\'han suprimit les categories</string>
<string name="dialog_with_checkbox_remove_description">S\'eliminarà la data de lectura d\'aquest capítol. N\'esteu segur\?</string> <string name="dialog_with_checkbox_remove_description">S\'eliminarà la data de lectura d\'aquest capítol. N\'esteu segur\?</string>
<string name="dialog_with_checkbox_reset">Reinicia tots els capítols d\'aquest manga</string> <string name="dialog_with_checkbox_reset">Reinicia tots els capítols d\'aquest element</string>
<string name="snack_add_to_library">Voleu afegir el manga a la biblioteca\?</string> <string name="snack_add_to_library">Voleu afegir-lo a la biblioteca\?</string>
<string name="picture_saved">S\'ha desat la imatge</string> <string name="picture_saved">S\'ha desat la imatge</string>
<string name="custom_filter">Filtre personalitzat</string> <string name="custom_filter">Filtre personalitzat</string>
<string name="set_as_cover">Defineix com a portada</string> <string name="set_as_cover">Defineix com a portada</string>
@ -268,14 +268,14 @@
<string name="download_queue_error">No s\'han pogut baixar els capítols. Podeu tornar-ho a provar a la secció de baixades</string> <string name="download_queue_error">No s\'han pogut baixar els capítols. Podeu tornar-ho a provar a la secció de baixades</string>
<string name="notification_new_chapters">S\'han trobat nous capítols</string> <string name="notification_new_chapters">S\'han trobat nous capítols</string>
<string name="notification_cover_update_failed">No s\'ha pogut actualitzar la portada</string> <string name="notification_cover_update_failed">No s\'ha pogut actualitzar la portada</string>
<string name="notification_first_add_to_library">Afegiu el manga a la vostra biblioteca abans de fer això</string> <string name="notification_first_add_to_library">Afegiu l\'element a la vostra biblioteca abans de fer això</string>
<string name="file_select_cover">Seleccioneu la imatge de portada</string> <string name="file_select_cover">Seleccioneu la imatge de portada</string>
<string name="file_select_backup">Seleccioneu el fitxer de còpia de seguretat</string> <string name="file_select_backup">Seleccioneu el fitxer de còpia de seguretat</string>
<string name="update_check_confirm">Baixa</string> <string name="update_check_confirm">Baixa</string>
<string name="update_check_no_new_updates">No hi ha cap nova actualització disponible</string> <string name="update_check_no_new_updates">No hi ha cap nova actualització disponible</string>
<string name="update_check_look_for_updates">S\'estan cercant actualitzacions…</string> <string name="update_check_look_for_updates">S\'estan cercant actualitzacions…</string>
<string name="update_check_notification_download_in_progress">S\'està baixant…</string> <string name="update_check_notification_download_in_progress">S\'està baixant…</string>
<string name="update_check_notification_download_complete">Premeu per a instal·lar</string> <string name="update_check_notification_download_complete">Premeu per a instal·lar l\'actualització</string>
<string name="update_check_notification_download_error">Error de baixada</string> <string name="update_check_notification_download_error">Error de baixada</string>
<string name="update_check_notification_update_available">Nova versió disponible!</string> <string name="update_check_notification_update_available">Nova versió disponible!</string>
<string name="information_no_downloads">No hi ha baixades</string> <string name="information_no_downloads">No hi ha baixades</string>
@ -302,13 +302,13 @@
<string name="filter_mode_darken">Crema / Enfosqueix</string> <string name="filter_mode_darken">Crema / Enfosqueix</string>
<string name="label_help">Ajuda</string> <string name="label_help">Ajuda</string>
<string name="no_results_found">No s\'ha trobat cap resultat</string> <string name="no_results_found">No s\'ha trobat cap resultat</string>
<string name="migration_selection_prompt">Seleccioneu un origen del qual vulgueu migrar</string> <string name="migration_selection_prompt">Seleccioneu una font a migrar</string>
<string name="action_webview_back">Endarrere</string> <string name="action_webview_back">Endarrere</string>
<string name="action_webview_forward">Endavant</string> <string name="action_webview_forward">Endavant</string>
<string name="action_webview_refresh">Actualitza</string> <string name="action_webview_refresh">Actualitza</string>
<string name="pref_category_library">Biblioteca</string> <string name="pref_category_library">Biblioteca</string>
<string name="ext_obsolete">Obsoleta</string> <string name="ext_obsolete">Obsoleta</string>
<string name="obsolete_extension_message">Aquesta extensió ja no està disponible.</string> <string name="obsolete_extension_message">Aquesta extensió ja no està disponible. És possible que no funcioni correctament i pot causar problemes a l\'aplicació. És recomanable que la desinstal·leu.</string>
<string name="pref_date_format">Format de data</string> <string name="pref_date_format">Format de data</string>
<string name="pref_category_library_update">Actualització global</string> <string name="pref_category_library_update">Actualització global</string>
<string name="logout_title">Voleu tancar la sessió a %1$s\?</string> <string name="logout_title">Voleu tancar la sessió a %1$s\?</string>
@ -361,8 +361,8 @@
<string name="email">Adreça electrònica</string> <string name="email">Adreça electrònica</string>
<string name="pref_always_show_chapter_transition">Mostra sempre la transició de capítol</string> <string name="pref_always_show_chapter_transition">Mostra sempre la transició de capítol</string>
<plurals name="notification_new_chapters_summary"> <plurals name="notification_new_chapters_summary">
<item quantity="one">Per a %d títol</item> <item quantity="one">Per a %d element</item>
<item quantity="other">Per a %d títols</item> <item quantity="other">Per a %d elements</item>
</plurals> </plurals>
<string name="action_menu">Menú</string> <string name="action_menu">Menú</string>
<string name="action_reorganize_by">Reordena</string> <string name="action_reorganize_by">Reordena</string>
@ -382,7 +382,7 @@
<string name="pref_skip_filtered_chapters">Omet els capítols filtrats</string> <string name="pref_skip_filtered_chapters">Omet els capítols filtrats</string>
<string name="label_sources">Fonts</string> <string name="label_sources">Fonts</string>
<string name="action_select_inverse">Inverteix la selecció</string> <string name="action_select_inverse">Inverteix la selecció</string>
<string name="pinned_sources">Fixat</string> <string name="pinned_sources">Fixades</string>
<string name="action_pin">Fixa</string> <string name="action_pin">Fixa</string>
<string name="add_tracking">Segueix</string> <string name="add_tracking">Segueix</string>
<string name="webtoon_side_padding_25">25%</string> <string name="webtoon_side_padding_25">25%</string>
@ -410,10 +410,10 @@
<string name="restore_in_progress">Ja s\'està fent una restauració</string> <string name="restore_in_progress">Ja s\'està fent una restauració</string>
<string name="backup_in_progress">Ja s\'està fent una còpia de seguretat</string> <string name="backup_in_progress">Ja s\'està fent una còpia de seguretat</string>
<string name="local_source_help_guide">Guia de fonts locals</string> <string name="local_source_help_guide">Guia de fonts locals</string>
<string name="last_used_source">Utilitzada per darrer cop</string> <string name="last_used_source">Utilitzada per darrera vegada</string>
<string name="check_for_updates">Comprova si hi ha actualitzacions</string> <string name="check_for_updates">Comprova si hi ha actualitzacions</string>
<string name="restore_duration">%02d min i %02d s</string> <string name="restore_duration">%02d min i %02d s</string>
<string name="downloaded_only_summary">Filtra tot el manga de la vostra biblioteca</string> <string name="downloaded_only_summary">Filtra tots els elements de la vostra biblioteca</string>
<plurals name="download_queue_summary"> <plurals name="download_queue_summary">
<item quantity="one">En resta %1$s</item> <item quantity="one">En resta %1$s</item>
<item quantity="other">En resten %1$s</item> <item quantity="other">En resten %1$s</item>
@ -433,14 +433,14 @@
<item quantity="other">%d categories</item> <item quantity="other">%d categories</item>
</plurals> </plurals>
<string name="action_display_unread_badge">Capítols no llegits</string> <string name="action_display_unread_badge">Capítols no llegits</string>
<string name="tracking_info">Sincronització unidireccional per a actualitzar el progrés dels capítols als serveis de seguiment. Configureu el seguiment d\'entrades individuals de manga al seu botó de seguiment.</string> <string name="tracking_info">La sincronització és unidireccional per a actualitzar el progrés dels capítols als serveis de seguiment. Configureu el seguiment d\'elements individuals al seu botó de seguiment.</string>
<string name="pref_refresh_library_covers">Refresca les portades de la biblioteca</string> <string name="pref_refresh_library_covers">Refresca les portades de la biblioteca</string>
<string name="unofficial_extension_message">Aquesta extensió no pertany a la llista d\'extensions oficials del Tachiyomi.</string> <string name="unofficial_extension_message">Aquesta extensió no pertany a la llista d\'extensions oficials del Tachiyomi.</string>
<string name="ext_unofficial">No oficial</string> <string name="ext_unofficial">No oficial</string>
<string name="sort_by_upload_date">Per data de pujada</string> <string name="sort_by_upload_date">Per data de pujada</string>
<string name="label_data">Dades</string> <string name="label_data">Dades</string>
<string name="backup_restore_missing_sources">Manquen fonts:</string> <string name="backup_restore_missing_sources">Manquen fonts:</string>
<string name="invalid_backup_file_missing_manga">La còpia de seguretat no conté cap manga.</string> <string name="invalid_backup_file_missing_manga">La còpia de seguretat no conté cap element de la biblioteca.</string>
<string name="invalid_backup_file">Fitxer de còpia de seguretat invàlid</string> <string name="invalid_backup_file">Fitxer de còpia de seguretat invàlid</string>
<string name="pref_library_update_refresh_metadata_summary">Comprova si hi ha noves portades o detalls en actualitzar la biblioteca</string> <string name="pref_library_update_refresh_metadata_summary">Comprova si hi ha noves portades o detalls en actualitzar la biblioteca</string>
<string name="pref_library_update_refresh_metadata">Refresca les metadades automàticament</string> <string name="pref_library_update_refresh_metadata">Refresca les metadades automàticament</string>
@ -499,13 +499,13 @@
<string name="no_chapters_error">No s\'ha trobat cap capítol</string> <string name="no_chapters_error">No s\'ha trobat cap capítol</string>
<string name="chapter_settings_updated">S\'ha actualitzat la configuració per defecte dels capítols</string> <string name="chapter_settings_updated">S\'ha actualitzat la configuració per defecte dels capítols</string>
<string name="set_chapter_settings_as_default">Estableix com a per defecte</string> <string name="set_chapter_settings_as_default">Estableix com a per defecte</string>
<string name="also_set_chapter_settings_for_library">Aplica-ho també a tot el manga de la biblioteca</string> <string name="also_set_chapter_settings_for_library">Aplica-ho també a tots els elements de la biblioteca</string>
<string name="confirm_set_chapter_settings">Esteu segur que voleu desar aquesta configuració com a configuració per defecte\?</string> <string name="confirm_set_chapter_settings">Esteu segur que voleu desar aquesta configuració com a configuració per defecte\?</string>
<string name="chapter_settings">Configuració dels capítols</string> <string name="chapter_settings">Configuració dels capítols</string>
<string name="share_page_info">%1$s: %2$s, pàgina %3$d</string> <string name="share_page_info">%1$s: %2$s, pàgina %3$d</string>
<string name="action_search_settings">Configuració de la cerca</string> <string name="action_search_settings">Configuració de la cerca</string>
<string name="downloaded_chapters">Capítols baixats</string> <string name="downloaded_chapters">Capítols baixats</string>
<string name="manga_from_library">Manga de la biblioteca</string> <string name="manga_from_library">De la biblioteca</string>
<string name="clear_history_confirmation">Nesteu segur\? Es perdrà tot lhistorial.</string> <string name="clear_history_confirmation">Nesteu segur\? Es perdrà tot lhistorial.</string>
<string name="pref_incognito_mode_summary">Pausa l\'historial de lectura</string> <string name="pref_incognito_mode_summary">Pausa l\'historial de lectura</string>
<string name="pref_incognito_mode">Mode d\'incògnit</string> <string name="pref_incognito_mode">Mode d\'incògnit</string>
@ -548,8 +548,8 @@
<string name="clipboard_copy_error">No s\'ha pogut copiar al porta-retalls</string> <string name="clipboard_copy_error">No s\'ha pogut copiar al porta-retalls</string>
<string name="notification_incognito_text">Desactiva el mode d\'incògnit</string> <string name="notification_incognito_text">Desactiva el mode d\'incògnit</string>
<string name="pref_dns_over_https">DNS sobre HTTPS (DoH)</string> <string name="pref_dns_over_https">DNS sobre HTTPS (DoH)</string>
<string name="pref_download_new_categories_details">El manga de les categories excloses no es baixarà encara que també sigui a les categories incloses.</string> <string name="pref_download_new_categories_details">Els elements de les categories excloses no es baixaran encara que també siguin a les categories incloses.</string>
<string name="pref_library_update_categories_details">El manga de les categories excloses no s\'actualitzarà encara que també sigui a les categories incloses.</string> <string name="pref_library_update_categories_details">Els elements de les categories excloses no s\'actualitzaran encara que també siguin a les categories incloses.</string>
<string name="pref_category_auto_download">Baixada automàtica</string> <string name="pref_category_auto_download">Baixada automàtica</string>
<string name="rotation_landscape">Horitzontal</string> <string name="rotation_landscape">Horitzontal</string>
<string name="rotation_portrait">Vertical</string> <string name="rotation_portrait">Vertical</string>
@ -559,7 +559,7 @@
<string name="nav_zone_next">Següent</string> <string name="nav_zone_next">Següent</string>
<string name="nav_zone_prev">Anterior</string> <string name="nav_zone_prev">Anterior</string>
<string name="automatic_background">Automàtic</string> <string name="automatic_background">Automàtic</string>
<string name="pref_create_folder_per_manga_summary">Crea carpetes segons el títol del manga</string> <string name="pref_create_folder_per_manga_summary">Crea carpetes segons el títol dels elements</string>
<string name="pref_create_folder_per_manga">Desa les pàgines en carpetes separades</string> <string name="pref_create_folder_per_manga">Desa les pàgines en carpetes separades</string>
<string name="pref_reader_actions">Accions</string> <string name="pref_reader_actions">Accions</string>
<string name="pref_grayscale">Escala de grisos</string> <string name="pref_grayscale">Escala de grisos</string>
@ -570,7 +570,7 @@
<string name="none">Cap</string> <string name="none">Cap</string>
<string name="action_show_errors">Premeu per a veure\'n els detalls</string> <string name="action_show_errors">Premeu per a veure\'n els detalls</string>
<string name="cancel_all_for_series">Cancel·la-ho tot per a aquesta sèrie</string> <string name="cancel_all_for_series">Cancel·la-ho tot per a aquesta sèrie</string>
<string name="action_sort_chapter_fetch_date">Data d\'obtenció del capítol</string> <string name="action_sort_chapter_fetch_date">Data d\'obtenció dels capítols</string>
<string name="date">Data</string> <string name="date">Data</string>
<string name="local_filter_order_by">Ordena per</string> <string name="local_filter_order_by">Ordena per</string>
<string name="local_invalid_format">El format del capítol no és vàlid</string> <string name="local_invalid_format">El format del capítol no és vàlid</string>
@ -586,7 +586,7 @@
<string name="on">Activat</string> <string name="on">Activat</string>
<string name="categorized_display_settings">Opcions de visualització i ordenació per categoria</string> <string name="categorized_display_settings">Opcions de visualització i ordenació per categoria</string>
<string name="restrictions">Restriccions: %s</string> <string name="restrictions">Restriccions: %s</string>
<string name="action_display_local_badge">Manga local</string> <string name="action_display_local_badge">Font local</string>
<string name="pref_lowest">La més baixa</string> <string name="pref_lowest">La més baixa</string>
<string name="pref_low">Baixa</string> <string name="pref_low">Baixa</string>
<string name="pref_high">Alta</string> <string name="pref_high">Alta</string>
@ -606,7 +606,7 @@
<string name="theme_greenapple">Verd poma</string> <string name="theme_greenapple">Verd poma</string>
<string name="pref_category_appearance">Aparença</string> <string name="pref_category_appearance">Aparença</string>
<string name="action_start_downloading_now">Inicia la baixada ara</string> <string name="action_start_downloading_now">Inicia la baixada ara</string>
<string name="action_sort_count">Nombre total de manga</string> <string name="action_sort_count">Nombre total d\'elements</string>
<string name="pref_app_theme">Tema de l\'aplicació</string> <string name="pref_app_theme">Tema de l\'aplicació</string>
<string name="theme_monet">Dinàmic</string> <string name="theme_monet">Dinàmic</string>
<string name="recently">Recentment</string> <string name="recently">Recentment</string>
@ -640,14 +640,14 @@
<string name="ext_installer_pref">Instal·lador</string> <string name="ext_installer_pref">Instal·lador</string>
<string name="ext_installer_legacy">Antic</string> <string name="ext_installer_legacy">Antic</string>
<string name="pref_auto_clear_chapter_cache">Buida la memòria cau de capítols en tancar l\'aplicació</string> <string name="pref_auto_clear_chapter_cache">Buida la memòria cau de capítols en tancar l\'aplicació</string>
<string name="enhanced_tracking_info">Serveix que proporcionen funcionalitats millorades per a fonts específiques. El manga se segueix automàticament en afegir-lo a la vostra biblioteca.</string> <string name="enhanced_tracking_info">Aquests serveis proporcionen funcionalitats millorades per a fonts específiques. Els elements se segueixen automàticament en afegir-los a la vostra biblioteca.</string>
<string name="about_dont_kill_my_app">Alguns fabricants tenen restriccions addicionals per a les aplicacions que finalitzen els serveis en segon pla. Aquest lloc web té més informació de com solucionar-ho.</string> <string name="about_dont_kill_my_app">Alguns fabricants tenen restriccions addicionals per a les aplicacions que finalitzen els serveis en segon pla. Aquest lloc web té més informació de com solucionar-ho.</string>
<string name="clear_database_source_item_count">Hi ha %1$d manga que no és a la biblioteca a la base de dades</string> <string name="clear_database_source_item_count">Hi ha %1$d elements a la base de dades que no són a la biblioteca</string>
<string name="cancelled">Cancel·lada</string> <string name="cancelled">Cancel·lada</string>
<string name="library_errors_help">Per a obtenir ajuda per a resoldre errors d\'actualització de la biblioteca, vegeu %1$s</string> <string name="library_errors_help">Per a obtenir ajuda per a resoldre errors d\'actualització de la biblioteca, vegeu %1$s</string>
<string name="extension_api_error">No s\'ha pogut obtenir la llista d\'extensions</string> <string name="extension_api_error">No s\'ha pogut obtenir la llista d\'extensions</string>
<string name="action_faq_and_guides">PMF i guies</string> <string name="action_faq_and_guides">PMF i guies</string>
<string name="pref_library_update_manga_restriction">Omet les actualitzacions dels títols</string> <string name="pref_library_update_manga_restriction">Omet les actualitzacions dels elements</string>
<string name="pref_update_only_completely_read">Amb capítols no llegits</string> <string name="pref_update_only_completely_read">Amb capítols no llegits</string>
<string name="pref_library_update_show_tab_badge">Mostra el comptador de no llegits a la icona d\'actualitzacions</string> <string name="pref_library_update_show_tab_badge">Mostra el comptador de no llegits a la icona d\'actualitzacions</string>
<string name="webtoon_side_padding_5">5%</string> <string name="webtoon_side_padding_5">5%</string>
@ -664,7 +664,7 @@
<string name="download_queue_size_warning">Advertència: Les baixades massa grosses poden fer que les fonts es tornin més lentes i/o bloquin el Tachiyomi. Premeu per a obtenir-ne més informació.</string> <string name="download_queue_size_warning">Advertència: Les baixades massa grosses poden fer que les fonts es tornin més lentes i/o bloquin el Tachiyomi. Premeu per a obtenir-ne més informació.</string>
<string name="notification_size_warning">Les actualitzacions grosses perjudiquen les fonts i poden implicar actualitzacions més lentes i un augment en l\'ús de bateria. Premeu per a obtenir-ne més informació.</string> <string name="notification_size_warning">Les actualitzacions grosses perjudiquen les fonts i poden implicar actualitzacions més lentes i un augment en l\'ús de bateria. Premeu per a obtenir-ne més informació.</string>
<string name="pref_landscape_zoom">Amplia la imatge en horitzontal</string> <string name="pref_landscape_zoom">Amplia la imatge en horitzontal</string>
<string name="action_show_manga">Mostra el manga</string> <string name="action_show_manga">Mostra l\'element</string>
<string name="confirm_manga_add_duplicate">Teniu un element a la vostra biblioteca amb el mateix nom però d\'una font diferent (%1$s). <string name="confirm_manga_add_duplicate">Teniu un element a la vostra biblioteca amb el mateix nom però d\'una font diferent (%1$s).
\n \n
\nVoleu continuar igualment\?</string> \nVoleu continuar igualment\?</string>
@ -695,7 +695,7 @@
<string name="source_filter_empty_screen">No s\'ha trobat cap font instal·lada</string> <string name="source_filter_empty_screen">No s\'ha trobat cap font instal·lada</string>
<string name="source_empty_screen">No s\'ha trobat cap font</string> <string name="source_empty_screen">No s\'ha trobat cap font</string>
<string name="action_sort_unread_count">Nombre de no llegits</string> <string name="action_sort_unread_count">Nombre de no llegits</string>
<string name="action_sort_last_manga_update">Darrera actualització de manga</string> <string name="action_sort_last_manga_update">Darrera comprovació d\'actualitzacions</string>
<string name="split_tall_images">Divideix les imatges altes</string> <string name="split_tall_images">Divideix les imatges altes</string>
<string name="split_tall_images_summary">Millora el rendiment del lector</string> <string name="split_tall_images_summary">Millora el rendiment del lector</string>
<string name="download_notifier_split_page_not_found">No s\'ha trobat la pàgina %d en dividir</string> <string name="download_notifier_split_page_not_found">No s\'ha trobat la pàgina %d en dividir</string>
@ -728,7 +728,7 @@
<string name="pref_reset_user_agent_string">Restableix la cadena d\'agent d\'usuari per defecte</string> <string name="pref_reset_user_agent_string">Restableix la cadena d\'agent d\'usuari per defecte</string>
<string name="appwidget_unavailable_locked">El widget no està disponible quan hi ha activat el blocatge de l\'aplicació</string> <string name="appwidget_unavailable_locked">El widget no està disponible quan hi ha activat el blocatge de l\'aplicació</string>
<string name="loader_rar5_error">El format RARv5 no està suportat</string> <string name="loader_rar5_error">El format RARv5 no està suportat</string>
<string name="appwidget_updates_description">Vegeu els vostres mangues actualitzats recentment</string> <string name="appwidget_updates_description">Vegeu els elements de la biblioteca actualitzats recentment</string>
<string name="action_remove_everything">Elimina-ho tot</string> <string name="action_remove_everything">Elimina-ho tot</string>
<string name="update_already_running">Ja s\'està executant una actualització</string> <string name="update_already_running">Ja s\'està executant una actualització</string>
<string name="theme_tidalwave">Tsunami</string> <string name="theme_tidalwave">Tsunami</string>
@ -746,7 +746,7 @@
<item quantity="other">Els següents %d capítols no llegits</item> <item quantity="other">Els següents %d capítols no llegits</item>
</plurals> </plurals>
<string name="missing_storage_permission">No s\'han concedit permisos d\'emmagatzematge</string> <string name="missing_storage_permission">No s\'han concedit permisos d\'emmagatzematge</string>
<string name="popular">Popular</string> <string name="popular">Populars</string>
<string name="pref_backup_summary">Còpies de seguretat automàtiques i manuals</string> <string name="pref_backup_summary">Còpies de seguretat automàtiques i manuals</string>
<string name="pref_reader_summary">Mode de lectura, visualització i navegació</string> <string name="pref_reader_summary">Mode de lectura, visualització i navegació</string>
<string name="pref_downloads_summary">Baixades automàtiques i per avançat</string> <string name="pref_downloads_summary">Baixades automàtiques i per avançat</string>
@ -765,5 +765,11 @@
<string name="remove_manga">Suprimireu «%s» de la vostra biblioteca</string> <string name="remove_manga">Suprimireu «%s» de la vostra biblioteca</string>
<string name="pref_long_strip_split">Divideix les imatges altes (BETA)</string> <string name="pref_long_strip_split">Divideix les imatges altes (BETA)</string>
<string name="action_search_hint">Cerca…</string> <string name="action_search_hint">Cerca…</string>
<string name="updates_last_update_info">Biblioteca actualitzada per darrer cop: %s</string> <string name="updates_last_update_info">Biblioteca actualitzada per darrera vegada: %s</string>
<string name="download_notifier_cache_renewal">S\'estan indexant les baixades</string>
<string name="channel_downloader_cache">Memòria cau de baixades</string>
<string name="action_open_random_manga">Obre un element aleatori</string>
<string name="fdroid_warning">Les compilacions de F-Droid no tenen assistència oficial.
\nPremeu per a obtenir-ne més informació.</string>
<string name="information_no_entries_found">No s\'ha trobat cap element en aquesta categoria</string>
</resources> </resources>

View File

@ -145,7 +145,7 @@
<string name="update_check_confirm">Stáhnout</string> <string name="update_check_confirm">Stáhnout</string>
<string name="update_check_no_new_updates">Žádné nové aktualizace</string> <string name="update_check_no_new_updates">Žádné nové aktualizace</string>
<string name="update_check_look_for_updates">Hledání aktualizací…</string> <string name="update_check_look_for_updates">Hledání aktualizací…</string>
<string name="update_check_notification_download_complete">Klepnutím nainstalujte</string> <string name="update_check_notification_download_complete">Klepnutím nainstalujte aktualizaci</string>
<string name="update_check_notification_download_error">Chyba při stahování</string> <string name="update_check_notification_download_error">Chyba při stahování</string>
<string name="update_check_notification_update_available">Dostupná aktualizace!</string> <string name="update_check_notification_update_available">Dostupná aktualizace!</string>
<string name="information_no_recent">Žádné nedávné aktualizace</string> <string name="information_no_recent">Žádné nedávné aktualizace</string>
@ -308,7 +308,7 @@
<string name="action_webview_refresh">Obnovit</string> <string name="action_webview_refresh">Obnovit</string>
<string name="pref_category_library">Knihovna</string> <string name="pref_category_library">Knihovna</string>
<string name="ext_obsolete">Zastaralý</string> <string name="ext_obsolete">Zastaralý</string>
<string name="obsolete_extension_message">Tohle rozšíření již není dostupné.</string> <string name="obsolete_extension_message">Toto rozšíření již není k dispozici. Nemusí fungovat správně a může způsobit problémy s aplikací. Doporučujeme jej odinstalovat.</string>
<string name="logout">Odhlásit se</string> <string name="logout">Odhlásit se</string>
<string name="pref_date_format">Formát data</string> <string name="pref_date_format">Formát data</string>
<string name="pref_category_library_update">Globální aktualizace</string> <string name="pref_category_library_update">Globální aktualizace</string>
@ -488,9 +488,9 @@
<item quantity="other">%1$d nových kapitol</item> <item quantity="other">%1$d nových kapitol</item>
</plurals> </plurals>
<plurals name="notification_new_chapters_summary"> <plurals name="notification_new_chapters_summary">
<item quantity="one">Pro %d titul</item> <item quantity="one">Pro %d položku</item>
<item quantity="few">Pro %d tituly</item> <item quantity="few">Pro %d položky</item>
<item quantity="other">Pro %d titulů</item> <item quantity="other">Pro %d položek</item>
</plurals> </plurals>
<string name="notification_check_updates">Hledám nové kapitoly</string> <string name="notification_check_updates">Hledám nové kapitoly</string>
<string name="download_insufficient_space">Nelze stáhnout kapitoly kvůli nedostatku místa</string> <string name="download_insufficient_space">Nelze stáhnout kapitoly kvůli nedostatku místa</string>
@ -567,7 +567,7 @@
<string name="rotation_landscape">Na šířku</string> <string name="rotation_landscape">Na šířku</string>
<string name="rotation_portrait">Na výšku</string> <string name="rotation_portrait">Na výšku</string>
<string name="rotation_type">Typ otočení</string> <string name="rotation_type">Typ otočení</string>
<string name="pref_create_folder_per_manga_summary">Vytváří složky podle názvů položek</string> <string name="pref_create_folder_per_manga_summary">Vytváří složky podle názvu položky</string>
<string name="pref_create_folder_per_manga">Uložit stránky do samostatných složek</string> <string name="pref_create_folder_per_manga">Uložit stránky do samostatných složek</string>
<string name="pref_reader_actions">Akce</string> <string name="pref_reader_actions">Akce</string>
<string name="nav_zone_right">Vpravo</string> <string name="nav_zone_right">Vpravo</string>
@ -660,12 +660,12 @@
<string name="pref_verbose_logging_summary">Vypisovat podrobné informace do systémového protokolu (sníží výkon aplikace)</string> <string name="pref_verbose_logging_summary">Vypisovat podrobné informace do systémového protokolu (sníží výkon aplikace)</string>
<string name="channel_app_updates">Aktualizace aplikace</string> <string name="channel_app_updates">Aktualizace aplikace</string>
<string name="notification_size_warning">Varování: velké aktualizace poškozují zdroje a můžou vést k pomalejším aktualizacím a zvýšenému využití baterie. Klepnutím se dozvíte více.</string> <string name="notification_size_warning">Varování: velké aktualizace poškozují zdroje a můžou vést k pomalejším aktualizacím a zvýšenému využití baterie. Klepnutím se dozvíte více.</string>
<string name="pref_auto_clear_chapter_cache">Vymazat cache kapitol při zavření aplikace</string> <string name="pref_auto_clear_chapter_cache">Vymazat mezipaměť kapitol při zavření aplikace</string>
<string name="extension_api_error">Chyba v získání seznamu rozšíření</string> <string name="extension_api_error">Chyba v získání seznamu rozšíření</string>
<string name="privacy_policy">Zásady ochrany osobních údajů</string> <string name="privacy_policy">Zásady ochrany osobních údajů</string>
<string name="clear_database_source_item_count">%1$d neknihovní záznamy v databázi</string> <string name="clear_database_source_item_count">%1$d neknihovní záznamy v databázi</string>
<string name="pref_true_color_summary">Snižuje pruhování barev, ale může mít vliv na výkon</string> <string name="pref_true_color_summary">Snižuje pruhování barev, ale může mít vliv na výkon</string>
<string name="pref_library_update_manga_restriction">Přeskočit aktualizaci titulů</string> <string name="pref_library_update_manga_restriction">Přeskočit aktualizování položek</string>
<string name="pref_update_only_completely_read">S nepřečtenými kapitolami</string> <string name="pref_update_only_completely_read">S nepřečtenými kapitolami</string>
<string name="database_clean">Nic k vyčištění</string> <string name="database_clean">Nic k vyčištění</string>
<string name="save_chapter_as_cbz">Uložit jako CBZ archiv</string> <string name="save_chapter_as_cbz">Uložit jako CBZ archiv</string>
@ -779,4 +779,10 @@
<string name="unknown_title">Neznámý titul</string> <string name="unknown_title">Neznámý titul</string>
<string name="error_user_agent_string_invalid">Neplatný řetězec uživatelského agenta</string> <string name="error_user_agent_string_invalid">Neplatný řetězec uživatelského agenta</string>
<string name="updates_last_update_info_just_now">Právě teď</string> <string name="updates_last_update_info_just_now">Právě teď</string>
<string name="download_notifier_cache_renewal">Indexování stažených souborů</string>
<string name="channel_downloader_cache">Stáhnout mezipaměť</string>
<string name="information_no_entries_found">V této kategorii nebyly nalezeny žádné položky</string>
<string name="action_open_random_manga">Otevřít náhodnou položku</string>
<string name="fdroid_warning">F-Droid sestavení nejsou oficiálně podporovány.
\nKlepnutím zobrazíte další informace.</string>
</resources> </resources>

View File

@ -559,4 +559,31 @@
<string name="pref_show_navigation_mode_summary">Пусма вырӑнсене вулӑш уҫӑ чухне кӑтартмалла</string> <string name="pref_show_navigation_mode_summary">Пусма вырӑнсене вулӑш уҫӑ чухне кӑтартмалла</string>
<string name="action_show_errors">Йӑнӑшсене кӑтарт</string> <string name="action_show_errors">Йӑнӑшсене кӑтарт</string>
<string name="cancel_all_for_series">Ҫак серилӗх валли веҫ пӑрахӑҫла</string> <string name="cancel_all_for_series">Ҫак серилӗх валли веҫ пӑрахӑҫла</string>
<string name="pref_downloads_summary">Хӑй-хальлӗн тийесе илни, малтан тийени</string>
<string name="action_sort_last_manga_update">Юлашки ҫӗнетӗве тӗрӗслени</string>
<string name="action_sort_unread_count">Юлнӑ сыпӑксем</string>
<string name="action_sort_count">Пурӗ ҫырав</string>
<string name="action_remove_everything">Веҫех катерт</string>
<string name="delete_category_confirmation">«%s» пухмӑша катертесшӗнех-и\?</string>
<string name="delete_category">Пухмӑша катерт</string>
<string name="action_display_local_badge">Вырӑнти ҫӑл куҫ</string>
<string name="label_warning">Асӑрхаттару</string>
<string name="action_display_cover_only_grid">Ятсӑр сетке</string>
<string name="action_move_to_top_all_for_series">Серие пуҫламӑша куҫар</string>
<string name="confirm_lock_change">Улшӑнӑва ҫирӗплетме аутентификацилен</string>
<string name="action_filter_started">Пуҫланӑ</string>
<string name="action_faq_and_guides">Кӑтартусем тата ыйту-хурав</string>
<string name="action_show_manga">Ҫырава кӑтарт</string>
<string name="action_display_language_badge">Чӗлхе</string>
<string name="action_search_hint">Шыра…</string>
<string name="action_close">Хуп</string>
<string name="action_start_downloading_now">Тиеве халь пуҫла</string>
<string name="internal_error">InternalError: Хушма пӗлӗме пӑхма тӑвӑмсен кӗнекине тӗрӗсле</string>
<string name="pref_category_appearance">Кӑтартӑнни</string>
<string name="on">Ҫутнӑ</string>
<string name="off">Сӳнтернӗ</string>
<string name="pref_appearance_summary">Тема, кун тата вӑхӑт хармачӗ</string>
<string name="pref_library_summary">Пухмӑшсем, пӗтӗмӗшле ҫӗнетӳ</string>
<string name="pref_reader_summary">Вулав тытӑмӗ, кӑтартӑнни, куҫӑм</string>
<string name="pref_general_summary">Хушӑм чӗлхи, систерӳсем</string>
</resources> </resources>

View File

@ -221,7 +221,7 @@
<string name="update_check_no_new_updates">Keine neue Aktualisierung verfügbar</string> <string name="update_check_no_new_updates">Keine neue Aktualisierung verfügbar</string>
<string name="update_check_look_for_updates">Suche nach Aktualisierungen…</string> <string name="update_check_look_for_updates">Suche nach Aktualisierungen…</string>
<string name="update_check_notification_download_in_progress">Herunterladen…</string> <string name="update_check_notification_download_in_progress">Herunterladen…</string>
<string name="update_check_notification_download_complete">Tippe zum Installieren</string> <string name="update_check_notification_download_complete">Tippe, um die Aktualisierung zu installieren</string>
<string name="update_check_notification_download_error">Fehler beim Herunterladen</string> <string name="update_check_notification_download_error">Fehler beim Herunterladen</string>
<string name="update_check_notification_update_available">Neue Version verfügbar!</string> <string name="update_check_notification_update_available">Neue Version verfügbar!</string>
<string name="information_no_downloads">Keine Downloads</string> <string name="information_no_downloads">Keine Downloads</string>
@ -308,7 +308,7 @@
<string name="action_webview_refresh">Aktualisieren</string> <string name="action_webview_refresh">Aktualisieren</string>
<string name="pref_category_library">Bibliothek</string> <string name="pref_category_library">Bibliothek</string>
<string name="ext_obsolete">Veraltet</string> <string name="ext_obsolete">Veraltet</string>
<string name="obsolete_extension_message">Diese Erweiterung ist nicht länger verfügbar.</string> <string name="obsolete_extension_message">Diese Erweiterung ist nicht länger verfügbar. Sie funktioniert möglicherweise nicht mehr ordnungsgemäß und kann Probleme mit der App verursachen. Es ist empfohlen, sie zu deinstallieren.</string>
<string name="pref_date_format">Datumsformat</string> <string name="pref_date_format">Datumsformat</string>
<string name="pref_category_library_update">Globale Aktualisierung</string> <string name="pref_category_library_update">Globale Aktualisierung</string>
<string name="logout_title">Aus %1$s abmelden\?</string> <string name="logout_title">Aus %1$s abmelden\?</string>
@ -361,8 +361,8 @@
<string name="email">E-Mail-Adresse</string> <string name="email">E-Mail-Adresse</string>
<string name="pref_always_show_chapter_transition">Kapitelübergang immer anzeigen</string> <string name="pref_always_show_chapter_transition">Kapitelübergang immer anzeigen</string>
<plurals name="notification_new_chapters_summary"> <plurals name="notification_new_chapters_summary">
<item quantity="one">Für %d Titel</item> <item quantity="one">Für %d Eintrag</item>
<item quantity="other">Für %d Titel</item> <item quantity="other">Für %d Einträge</item>
</plurals> </plurals>
<string name="action_menu">Menü</string> <string name="action_menu">Menü</string>
<string name="action_reorganize_by">Umordnen</string> <string name="action_reorganize_by">Umordnen</string>
@ -560,7 +560,7 @@
<string name="clipboard_copy_error">Kopieren in die Zwischenablage fehlgeschlagen</string> <string name="clipboard_copy_error">Kopieren in die Zwischenablage fehlgeschlagen</string>
<string name="rotation_landscape">Querformat</string> <string name="rotation_landscape">Querformat</string>
<string name="rotation_portrait">Hochformat</string> <string name="rotation_portrait">Hochformat</string>
<string name="pref_create_folder_per_manga_summary">Erstellt Ordner nach den Titeln der Einträge</string> <string name="pref_create_folder_per_manga_summary">Erstellt Ordner nach dem Titel der Einträge</string>
<string name="pref_create_folder_per_manga">Speichere Seiten in separate Ordner</string> <string name="pref_create_folder_per_manga">Speichere Seiten in separate Ordner</string>
<string name="rotation_type">Ausrichtungstyp</string> <string name="rotation_type">Ausrichtungstyp</string>
<string name="pref_reader_actions">Aktionen</string> <string name="pref_reader_actions">Aktionen</string>
@ -655,7 +655,7 @@
<string name="extension_api_error">Herunterladen der Erweiterungsliste ist fehlgeschlagen</string> <string name="extension_api_error">Herunterladen der Erweiterungsliste ist fehlgeschlagen</string>
<string name="privacy_policy">Datenschutzbestimmungen</string> <string name="privacy_policy">Datenschutzbestimmungen</string>
<string name="pref_update_only_completely_read">Mit ungelesenen Kapiteln</string> <string name="pref_update_only_completely_read">Mit ungelesenen Kapiteln</string>
<string name="pref_library_update_manga_restriction">Aktualisierung der Titel überspringen</string> <string name="pref_library_update_manga_restriction">Aktualisierung der Einträge überspringen</string>
<string name="library_errors_help">Für Hilfe zum Beheben von Fehlern bei Bibliotheksaktualisierungen, siehe %1$s</string> <string name="library_errors_help">Für Hilfe zum Beheben von Fehlern bei Bibliotheksaktualisierungen, siehe %1$s</string>
<string name="save_chapter_as_cbz">Als CBZ-Archiv speichern</string> <string name="save_chapter_as_cbz">Als CBZ-Archiv speichern</string>
<string name="cancelled">Abgebrochen</string> <string name="cancelled">Abgebrochen</string>
@ -766,4 +766,10 @@
<string name="invalid_location">Ungültiger Speicherort: %s</string> <string name="invalid_location">Ungültiger Speicherort: %s</string>
<string name="error_user_agent_string_invalid">Ungültiger User-Agent-Text</string> <string name="error_user_agent_string_invalid">Ungültiger User-Agent-Text</string>
<string name="updates_last_update_info_just_now">Gerade eben</string> <string name="updates_last_update_info_just_now">Gerade eben</string>
<string name="channel_downloader_cache">Download-Zwischenspeicher</string>
<string name="download_notifier_cache_renewal">Downloads werden indiziert</string>
<string name="action_open_random_manga">Zufälligen Eintrag öffnen</string>
<string name="information_no_entries_found">Keine Einträge in dieser Kategorie gefunden</string>
<string name="fdroid_warning">F-Droid-Builds werden nicht offiziell unterstützt.
\nTippe, um mehr zu erfahren.</string>
</resources> </resources>

View File

@ -5,7 +5,7 @@
<string name="manga">Manga</string> <string name="manga">Manga</string>
<string name="chapters">Κεφάλαια</string> <string name="chapters">Κεφάλαια</string>
<string name="track">Tracking</string> <string name="track">Tracking</string>
<string name="history">Ιστορία</string> <string name="history">Ιστορικό</string>
<string name="label_settings">Ρυθμίσεις</string> <string name="label_settings">Ρυθμίσεις</string>
<string name="label_download_queue">Ουρά λήψεων</string> <string name="label_download_queue">Ουρά λήψεων</string>
<string name="label_library">Βιβλιοθήκη</string> <string name="label_library">Βιβλιοθήκη</string>
@ -169,7 +169,7 @@
<string name="pref_backup_slots">Μέγιστα αντίγραφα ασφαλείας</string> <string name="pref_backup_slots">Μέγιστα αντίγραφα ασφαλείας</string>
<string name="backup_created">Δημιουργήθηκε αντίγραφο ασφαλείας</string> <string name="backup_created">Δημιουργήθηκε αντίγραφο ασφαλείας</string>
<string name="restore_completed">Η επαναφορά ολοκληρώθηκε</string> <string name="restore_completed">Η επαναφορά ολοκληρώθηκε</string>
<string name="backup_choice">Τι θέλετε να κάνετε backup;</string> <string name="backup_choice">Τι αντίγραφο ασφαλείας θέλετε να δημιουργήσετε;</string>
<string name="restoring_backup">Επαναφορά αντιγράφων ασφαλείας</string> <string name="restoring_backup">Επαναφορά αντιγράφων ασφαλείας</string>
<string name="creating_backup">Δημιουργία αντιγράφων ασφαλείας</string> <string name="creating_backup">Δημιουργία αντιγράφων ασφαλείας</string>
<string name="pref_clear_chapter_cache">Καθάρισμα προσωρινής μνήμης κεφαλαίου</string> <string name="pref_clear_chapter_cache">Καθάρισμα προσωρινής μνήμης κεφαλαίου</string>
@ -179,8 +179,8 @@
<string name="pref_clear_cookies">Διαγραφή cookies</string> <string name="pref_clear_cookies">Διαγραφή cookies</string>
<string name="cookies_cleared">Τα cookies διαγράφηκαν</string> <string name="cookies_cleared">Τα cookies διαγράφηκαν</string>
<string name="pref_clear_database">Καθαρισμός βάσης δεδομένων</string> <string name="pref_clear_database">Καθαρισμός βάσης δεδομένων</string>
<string name="pref_clear_database_summary">Διαγραφή ιστορικού για manga που δεν είναι αποθηκευμένα στη βιβλιοθήκη σας</string> <string name="pref_clear_database_summary">Διαγραφή ιστορικού για καταχωρήσεις που δεν έχουν αποθηκευτεί στη βιβλιοθήκη σας</string>
<string name="clear_database_confirmation">Είστε σίγουροι; Τα διαβασμένα κεφάλαια και η πρόοδος των manga εκτός βιβλιοθήκης θα χαθεί</string> <string name="clear_database_confirmation">Είστε σίγουροι; Τα διαβασμένα κεφάλαια και η πρόοδος των καταχωρήσεων εκτός βιβλιοθήκης θα χαθούν</string>
<string name="clear_database_completed">Οι καταχωρίσεις διαγράφηκαν</string> <string name="clear_database_completed">Οι καταχωρίσεις διαγράφηκαν</string>
<string name="pref_refresh_library_tracking">Ανανέωση tracking</string> <string name="pref_refresh_library_tracking">Ανανέωση tracking</string>
<string name="pref_refresh_library_tracking_summary">Ενημερώνει κατάσταση, βαθμολογία και τελευταίο αναγνωσμένο κεφάλαιο από τις υπηρεσίες παρακολούθησης</string> <string name="pref_refresh_library_tracking_summary">Ενημερώνει κατάσταση, βαθμολογία και τελευταίο αναγνωσμένο κεφάλαιο από τις υπηρεσίες παρακολούθησης</string>
@ -244,8 +244,8 @@
<string name="error_category_exists">Μια κατηγορία με αυτό το όνομα υπάρχει ήδη!</string> <string name="error_category_exists">Μια κατηγορία με αυτό το όνομα υπάρχει ήδη!</string>
<string name="snack_categories_deleted">Οι κατηγορίες διαγράφηκαν</string> <string name="snack_categories_deleted">Οι κατηγορίες διαγράφηκαν</string>
<string name="dialog_with_checkbox_remove_description">Αυτό θα αφαιρέσει την ημερομηνία ανάγνωσης αυτού του κεφαλαίου. Είστε σίγουροι;</string> <string name="dialog_with_checkbox_remove_description">Αυτό θα αφαιρέσει την ημερομηνία ανάγνωσης αυτού του κεφαλαίου. Είστε σίγουροι;</string>
<string name="dialog_with_checkbox_reset">Επαναφορά όλων των κεφαλαίων για αυτό το manga</string> <string name="dialog_with_checkbox_reset">Επαναφορά όλων των κεφαλαίων για αυτήν την καταχώρηση</string>
<string name="snack_add_to_library">Προσθήκη manga στη βιβλιοθήκη;</string> <string name="snack_add_to_library">Προσθήκη στη βιβλιοθήκη;</string>
<string name="picture_saved">Η εικόνα αποθηκεύτηκε</string> <string name="picture_saved">Η εικόνα αποθηκεύτηκε</string>
<string name="custom_filter">Προσαρμοσμένο φίλτρο</string> <string name="custom_filter">Προσαρμοσμένο φίλτρο</string>
<string name="set_as_cover">Ορισμός ως εξώφυλλο</string> <string name="set_as_cover">Ορισμός ως εξώφυλλο</string>
@ -268,14 +268,14 @@
<string name="download_queue_error">Δεν ήταν δυνατή η λήψη κεφαλαίων. Μπορείτε να δοκιμάσετε ξανά στο τμήμα λήψεων</string> <string name="download_queue_error">Δεν ήταν δυνατή η λήψη κεφαλαίων. Μπορείτε να δοκιμάσετε ξανά στο τμήμα λήψεων</string>
<string name="notification_new_chapters">Βρέθηκαν νέα κεφάλαια</string> <string name="notification_new_chapters">Βρέθηκαν νέα κεφάλαια</string>
<string name="notification_cover_update_failed">Δεν ήταν δυνατή η ενημέρωση του εξώφυλλου</string> <string name="notification_cover_update_failed">Δεν ήταν δυνατή η ενημέρωση του εξώφυλλου</string>
<string name="notification_first_add_to_library">Παρακαλώ προσθέστε το manga στη βιβλιοθήκη σας πριν κάνετε κάτι τέτοιο</string> <string name="notification_first_add_to_library">Παρακαλώ προσθέστε την καταχώρηση στη βιβλιοθήκη σας πριν το κάνετε αυτό</string>
<string name="file_select_cover">Επιλέξτε εικόνα εξωφύλλου</string> <string name="file_select_cover">Επιλέξτε εικόνα εξωφύλλου</string>
<string name="file_select_backup">Επιλέξτε αρχείο αντιγράφου ασφαλείας</string> <string name="file_select_backup">Επιλέξτε αρχείο αντιγράφου ασφαλείας</string>
<string name="update_check_confirm">Λήψη</string> <string name="update_check_confirm">Λήψη</string>
<string name="update_check_no_new_updates">Δεν υπάρχουν διαθέσιμες νέες ενημερώσεις</string> <string name="update_check_no_new_updates">Δεν υπάρχουν διαθέσιμες νέες ενημερώσεις</string>
<string name="update_check_look_for_updates">Αναζήτηση για ενημερώσεις…</string> <string name="update_check_look_for_updates">Αναζήτηση για ενημερώσεις…</string>
<string name="update_check_notification_download_in_progress">Γίνεται λήψη…</string> <string name="update_check_notification_download_in_progress">Γίνεται λήψη…</string>
<string name="update_check_notification_download_complete">Πατήστε για εγκατάσταση</string> <string name="update_check_notification_download_complete">Πατήστε για να εγκαταστήσετε την ενημέρωση</string>
<string name="update_check_notification_download_error">Σφάλμα λήψης</string> <string name="update_check_notification_download_error">Σφάλμα λήψης</string>
<string name="update_check_notification_update_available">Υπάρχει διαθέσιμη ενημέρωση!</string> <string name="update_check_notification_update_available">Υπάρχει διαθέσιμη ενημέρωση!</string>
<string name="information_no_downloads">Δεν υπάρχουν λήψεις</string> <string name="information_no_downloads">Δεν υπάρχουν λήψεις</string>
@ -308,7 +308,7 @@
<string name="action_webview_refresh">Ανανέωση</string> <string name="action_webview_refresh">Ανανέωση</string>
<string name="pref_category_library">Βιβλιοθήκη</string> <string name="pref_category_library">Βιβλιοθήκη</string>
<string name="ext_obsolete">Απαρχαιωμένο</string> <string name="ext_obsolete">Απαρχαιωμένο</string>
<string name="obsolete_extension_message">Αυτή η επέκταση δεν είναι πλέον διαθέσιμη.</string> <string name="obsolete_extension_message">Αυτή η επέκταση δεν είναι πλέον διαθέσιμη. Ενδέχεται να μη λειτουργεί σωστά και μπορεί να προκαλέσει προβλήματα με την εφαρμογή. Συνιστάται η απεγκατάσταση της.</string>
<string name="label_more">Περισσότερα</string> <string name="label_more">Περισσότερα</string>
<string name="last_used_source">Χρησιμοποιήθηκε τελευταία</string> <string name="last_used_source">Χρησιμοποιήθηκε τελευταία</string>
<string name="add_tracking">Προσθήκη tracking</string> <string name="add_tracking">Προσθήκη tracking</string>
@ -413,7 +413,7 @@
<item quantity="one">Κεφάλαια %1$s και 1 ακόμη</item> <item quantity="one">Κεφάλαια %1$s και 1 ακόμη</item>
<item quantity="other">Κεφάλαια %1$s και %2$d ακόμη</item> <item quantity="other">Κεφάλαια %1$s και %2$d ακόμη</item>
</plurals> </plurals>
<string name="downloaded_only_summary">Φιλτράρει όλα τα manga στη βιβλιοθήκη σας</string> <string name="downloaded_only_summary">Φιλτράρει όλες τις καταχωρήσεις στη βιβλιοθήκη σας</string>
<string name="pref_search_pinned_sources_only">Να περιέχονται μόνο καρφιτσωμένες πηγές</string> <string name="pref_search_pinned_sources_only">Να περιέχονται μόνο καρφιτσωμένες πηγές</string>
<plurals name="download_queue_summary"> <plurals name="download_queue_summary">
<item quantity="one">%1$s που απομένει</item> <item quantity="one">%1$s που απομένει</item>
@ -433,14 +433,14 @@
<item quantity="other">Έγινε σε %1$s με %2$s σφάλματα</item> <item quantity="other">Έγινε σε %1$s με %2$s σφάλματα</item>
</plurals> </plurals>
<string name="action_display_unread_badge">Μη αναγνωσμένα κεφάλαια</string> <string name="action_display_unread_badge">Μη αναγνωσμένα κεφάλαια</string>
<string name="tracking_info">Μονόδρομος συγχρονισμός για ενημέρωση των υπηρεσιών παρακολούθησης προόδου κεφαλαίων. Ρυθμίστε την παρακολούθηση για μεμονωμένες καταχωρήσεις manga από το κουμπί παρακολούθησης τους.</string> <string name="tracking_info">Μονόδρομος συγχρονισμός για ενημέρωση των υπηρεσιών παρακολούθησης προόδου κεφαλαίων. Ρυθμίστε την παρακολούθηση για μεμονωμένες καταχωρήσεις από το κουμπί παρακολούθησης τους.</string>
<string name="pref_refresh_library_covers">Ανανέωση εξώφυλλων βιβλιοθήκης</string> <string name="pref_refresh_library_covers">Ανανέωση εξώφυλλων βιβλιοθήκης</string>
<string name="unofficial_extension_message">Αυτή η επέκταση δεν προέρχεται από την επίσημη λίστα επεκτάσεων Tachiyomi.</string> <string name="unofficial_extension_message">Αυτή η επέκταση δεν προέρχεται από την επίσημη λίστα επεκτάσεων Tachiyomi.</string>
<string name="ext_unofficial">Ανεπίσημη</string> <string name="ext_unofficial">Ανεπίσημη</string>
<string name="sort_by_upload_date">Από ημερομηνία μεταφόρτωσης</string> <string name="sort_by_upload_date">Από ημερομηνία μεταφόρτωσης</string>
<string name="label_data">Δεδομένα</string> <string name="label_data">Δεδομένα</string>
<string name="backup_restore_missing_sources">Πηγές που λείπουν:</string> <string name="backup_restore_missing_sources">Πηγές που λείπουν:</string>
<string name="invalid_backup_file_missing_manga">Το αντίγραφο ασφαλείας δεν περιέχει manga.</string> <string name="invalid_backup_file_missing_manga">Το αντίγραφο ασφαλείας δεν περιέχει καταχωρήσεις βιβλιοθήκης.</string>
<string name="invalid_backup_file">Μη έγκυρο αρχείο αντιγράφου ασφαλείας</string> <string name="invalid_backup_file">Μη έγκυρο αρχείο αντιγράφου ασφαλείας</string>
<string name="pref_library_update_refresh_metadata_summary">Έλεγχος για νέο εξώφυλλο και λεπτομέρειες κατά την ενημέρωση της βιβλιοθήκης</string> <string name="pref_library_update_refresh_metadata_summary">Έλεγχος για νέο εξώφυλλο και λεπτομέρειες κατά την ενημέρωση της βιβλιοθήκης</string>
<string name="pref_library_update_refresh_metadata">Αυτόματη ανανέωση μεταδεδομένων</string> <string name="pref_library_update_refresh_metadata">Αυτόματη ανανέωση μεταδεδομένων</string>
@ -499,17 +499,17 @@
<string name="no_chapters_error">Δεν βρέθηκαν κεφάλαια</string> <string name="no_chapters_error">Δεν βρέθηκαν κεφάλαια</string>
<string name="chapter_settings_updated">Ενημερώθηκαν οι προεπιλεγμένες ρυθμίσεις κεφαλαίου</string> <string name="chapter_settings_updated">Ενημερώθηκαν οι προεπιλεγμένες ρυθμίσεις κεφαλαίου</string>
<string name="set_chapter_settings_as_default">Ορισμός ως προεπιλογής</string> <string name="set_chapter_settings_as_default">Ορισμός ως προεπιλογής</string>
<string name="also_set_chapter_settings_for_library">Εφαρμογή επίσης σε όλα τα manga στη βιβλιοθήκη μου</string> <string name="also_set_chapter_settings_for_library">Εφαρμογή επίσης για όλες τις καταχωρήσεις στη βιβλιοθήκη μου</string>
<string name="confirm_set_chapter_settings">Είστε σίγουροι ότι θέλετε να αποθηκεύσετε αυτές τις ρυθμίσεις ως προεπιλεγμένες;</string> <string name="confirm_set_chapter_settings">Είστε σίγουροι ότι θέλετε να αποθηκεύσετε αυτές τις ρυθμίσεις ως προεπιλεγμένες;</string>
<string name="chapter_settings">Ρυθμίσεις κεφαλαίου</string> <string name="chapter_settings">Ρυθμίσεις κεφαλαίου</string>
<string name="share_page_info">%1$s: %2$s, σελίδα %3$d</string> <string name="share_page_info">%1$s: %2$s, σελίδα %3$d</string>
<string name="action_search_settings">Αναζήτηση ρυθμίσεων</string> <string name="action_search_settings">Αναζήτηση ρυθμίσεων</string>
<string name="downloaded_chapters">Κεφάλαια που έχουν ληφθεί</string> <string name="downloaded_chapters">Κεφάλαια που έχουν ληφθεί</string>
<string name="manga_from_library">Manga από τη βιβλιοθήκη</string> <string name="manga_from_library">Από βιβλιοθήκη</string>
<string name="pref_incognito_mode_summary">Παύει το ιστορικό ανάγνωσης</string> <string name="pref_incognito_mode_summary">Παύει το ιστορικό ανάγνωσης</string>
<string name="pref_incognito_mode">Λειτουργία ανώνυμης περιήγησης</string> <string name="pref_incognito_mode">Λειτουργία ανώνυμης περιήγησης</string>
<string name="pref_clear_history">Διαγραφή ιστορικού</string> <string name="pref_clear_history">Διαγραφή ιστορικού</string>
<string name="clear_history_confirmation">Είστε σίγουροι\? Όλο το ιστορικό θα χαθεί.</string> <string name="clear_history_confirmation">Είστε σίγουροι; Όλο το ιστορικό θα χαθεί.</string>
<string name="clear_history_completed">Το ιστορικό διαγράφηκε</string> <string name="clear_history_completed">Το ιστορικό διαγράφηκε</string>
<string name="spen_next_page">Επόμενη σελίδα</string> <string name="spen_next_page">Επόμενη σελίδα</string>
<string name="spen_previous_page">Προηγούμενη σελίδα</string> <string name="spen_previous_page">Προηγούμενη σελίδα</string>
@ -552,15 +552,15 @@
<string name="include">Συμπερίληψη: %s</string> <string name="include">Συμπερίληψη: %s</string>
<string name="none">Κανένα</string> <string name="none">Κανένα</string>
<string name="action_sort_chapter_fetch_date">Ημερομηνία ανάκτησης κεφαλαίου</string> <string name="action_sort_chapter_fetch_date">Ημερομηνία ανάκτησης κεφαλαίου</string>
<string name="pref_download_new_categories_details">Τα Manga σε εξαιρούμενες κατηγορίες δεν θα ληφθούν ακόμα κι αν ανήκουν και σε κατηγορίες που περιλαμβάνονται.</string> <string name="pref_download_new_categories_details">Οι καταχωρήσεις σε εξαιρούμενες κατηγορίες δε θα ληφθούν ακόμη και αν βρίσκονται επίσης σε κατηγορίες που περιλαμβάνονται.</string>
<string name="pref_category_auto_download">Αυτόματη λήψη</string> <string name="pref_category_auto_download">Αυτόματη λήψη</string>
<string name="pref_library_update_categories_details">Τα manga στις αποκλεισμένες κατηγορίες δεν θα ενημερώνονται ακόμη και αν βρίσκονται επίσης στις συμπεριλαμβανόμενες κατηγορίες.</string> <string name="pref_library_update_categories_details">Οι καταχωρίσεις σε εξαιρούμενες κατηγορίες δε θα ενημερώνονται ακόμη και αν περιλαμβάνονται επίσης σε κατηγορίες που περιλαμβάνονται.</string>
<string name="action_show_errors">Πατήστε για να δείτε λεπτομέρειες</string> <string name="action_show_errors">Πατήστε για να δείτε λεπτομέρειες</string>
<string name="update_check_eol">Αυτή η έκδοση Android δεν υποστηρίζεται πλέον</string> <string name="update_check_eol">Αυτή η έκδοση Android δεν υποστηρίζεται πλέον</string>
<string name="clipboard_copy_error">Απέτυχε η αντιγραφή στο πρόχειρο</string> <string name="clipboard_copy_error">Απέτυχε η αντιγραφή στο πρόχειρο</string>
<string name="rotation_landscape">Οριζόντια</string> <string name="rotation_landscape">Οριζόντια</string>
<string name="rotation_portrait">Κατακόρυφα</string> <string name="rotation_portrait">Κατακόρυφα</string>
<string name="pref_create_folder_per_manga_summary">Δημιουργεί φακέλους σύμφωνα με τον τίτλο manga</string> <string name="pref_create_folder_per_manga_summary">Δημιουργεί φακέλους σύμφωνα με τους τίτλους των καταχωρήσεων</string>
<string name="pref_create_folder_per_manga">Αποθήκευση σελίδων σε ξεχωριστούς φακέλους</string> <string name="pref_create_folder_per_manga">Αποθήκευση σελίδων σε ξεχωριστούς φακέλους</string>
<string name="pref_reader_actions">Ενέργειες</string> <string name="pref_reader_actions">Ενέργειες</string>
<string name="rotation_type">Τύπος περιστροφής</string> <string name="rotation_type">Τύπος περιστροφής</string>
@ -578,7 +578,7 @@
<string name="local_invalid_format">Μη έγκυρη μορφή κεφαλαίου</string> <string name="local_invalid_format">Μη έγκυρη μορφή κεφαλαίου</string>
<string name="chapter_not_found">Το κεφάλαιο δε βρέθηκε</string> <string name="chapter_not_found">Το κεφάλαιο δε βρέθηκε</string>
<string name="restrictions">Περιορισμοί: %s</string> <string name="restrictions">Περιορισμοί: %s</string>
<string name="action_display_local_badge">Τοπικά manga</string> <string name="action_display_local_badge">Τοπική πηγή</string>
<string name="off">Απενεργοποιημένο</string> <string name="off">Απενεργοποιημένο</string>
<string name="on">Ενεργοποιημένο</string> <string name="on">Ενεργοποιημένο</string>
<string name="error_sharing_cover">Σφάλμα κοινής χρήσης του εξωφύλλου</string> <string name="error_sharing_cover">Σφάλμα κοινής χρήσης του εξωφύλλου</string>
@ -600,7 +600,7 @@
<string name="theme_yinyang">Γιν και Γιανγκ</string> <string name="theme_yinyang">Γιν και Γιανγκ</string>
<string name="theme_tako">Tako</string> <string name="theme_tako">Tako</string>
<string name="theme_strawberrydaiquiri">Φράουλα Daiquiri</string> <string name="theme_strawberrydaiquiri">Φράουλα Daiquiri</string>
<string name="enhanced_tracking_info">Υπηρεσίες που παρέχουν βελτιωμένες δυνατότητες για συγκεκριμένες πηγές. Τα manga παρακολουθούνται αυτόματα όταν προστίθεται στη βιβλιοθήκη σας.</string> <string name="enhanced_tracking_info">Υπηρεσίες που παρέχουν βελτιωμένες δυνατότητες για συγκεκριμένες πηγές. Οι καταχωρήσεις παρακολουθούνται αυτόματα όταν προστίθενται στη βιβλιοθήκη σας.</string>
<string name="tracker_komga_warning">Αυτό το tracker είναι συμβατό μόνο με την πηγή Komga.</string> <string name="tracker_komga_warning">Αυτό το tracker είναι συμβατό μόνο με την πηγή Komga.</string>
<string name="enhanced_services">Βελτιωμένες υπηρεσίες</string> <string name="enhanced_services">Βελτιωμένες υπηρεσίες</string>
<string name="theme_monet">Δυναμικό</string> <string name="theme_monet">Δυναμικό</string>
@ -636,7 +636,7 @@
<string name="ext_installer_shizuku_unavailable_dialog">Εγκαταστήστε και ξεκινήστε το Shizuku για να χρησιμοποιήσετε το Shizuku ως πρόγραμμα εγκατάστασης επεκτάσεων.</string> <string name="ext_installer_shizuku_unavailable_dialog">Εγκαταστήστε και ξεκινήστε το Shizuku για να χρησιμοποιήσετε το Shizuku ως πρόγραμμα εγκατάστασης επεκτάσεων.</string>
<string name="ext_installer_shizuku_stopped">Το Shizuku δεν τρέχει</string> <string name="ext_installer_shizuku_stopped">Το Shizuku δεν τρέχει</string>
<string name="ext_installer_legacy">Παλαιό</string> <string name="ext_installer_legacy">Παλαιό</string>
<string name="action_sort_count">Σύνολο manga</string> <string name="action_sort_count">Σύνολο καταχωρήσεων</string>
<string name="pref_verbose_logging">Λεπτομερής καταγραφή</string> <string name="pref_verbose_logging">Λεπτομερής καταγραφή</string>
<string name="pref_verbose_logging_summary">Εκτύπωση λεπτομερών αρχείων καταγραφής στο αρχείο καταγραφής συστήματος (μειώνει την απόδοση της εφαρμογής)</string> <string name="pref_verbose_logging_summary">Εκτύπωση λεπτομερών αρχείων καταγραφής στο αρχείο καταγραφής συστήματος (μειώνει την απόδοση της εφαρμογής)</string>
<string name="action_display_language_badge">Γλώσσα</string> <string name="action_display_language_badge">Γλώσσα</string>
@ -650,7 +650,7 @@
<string name="ext_update_all">Ενημέρωση όλων</string> <string name="ext_update_all">Ενημέρωση όλων</string>
<string name="channel_app_updates">Ενημερώσεις εφαρμογής</string> <string name="channel_app_updates">Ενημερώσεις εφαρμογής</string>
<string name="pref_auto_clear_chapter_cache">Εκκαθάριση της προσωρινής μνήμης κεφαλαίων στο κλείσιμο της εφαρμογής</string> <string name="pref_auto_clear_chapter_cache">Εκκαθάριση της προσωρινής μνήμης κεφαλαίων στο κλείσιμο της εφαρμογής</string>
<string name="clear_database_source_item_count">%1$d manga εκτός βιβλιοθήκης στη βάση δεδομένων</string> <string name="clear_database_source_item_count">%1$d καταχωρήσεις εκτός βιβλιοθήκης στη βάση δεδομένων</string>
<string name="database_clean">Τίποτα προς εκκαθάριση</string> <string name="database_clean">Τίποτα προς εκκαθάριση</string>
<string name="extension_api_error">Απέτυχε η λήψη λίστας επεκτάσεων</string> <string name="extension_api_error">Απέτυχε η λήψη λίστας επεκτάσεων</string>
<string name="privacy_policy">Πολιτική απορρήτου</string> <string name="privacy_policy">Πολιτική απορρήτου</string>
@ -663,7 +663,7 @@
<string name="publishing_finished">Η έκδοσή ολοκληρώθηκε</string> <string name="publishing_finished">Η έκδοσή ολοκληρώθηκε</string>
<string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</string> <string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</string>
<string name="webtoon_side_padding_5">5%</string> <string name="webtoon_side_padding_5">5%</string>
<string name="action_show_manga">Εμφάνιση manga</string> <string name="action_show_manga">Εμφάνιση καταχώρισης</string>
<string name="pref_landscape_zoom">Ζουμ οριζόντιας εικόνας</string> <string name="pref_landscape_zoom">Ζουμ οριζόντιας εικόνας</string>
<string name="confirm_manga_add_duplicate">Έχετε μια καταχώρηση στη βιβλιοθήκη σας με το ίδιο όνομα αλλά από διαφορετική πηγή (%1$s). <string name="confirm_manga_add_duplicate">Έχετε μια καταχώρηση στη βιβλιοθήκη σας με το ίδιο όνομα αλλά από διαφορετική πηγή (%1$s).
\n \n
@ -694,7 +694,7 @@
<string name="battery_not_low">Όταν η μπαταρία δεν είναι χαμηλή</string> <string name="battery_not_low">Όταν η μπαταρία δεν είναι χαμηλή</string>
<string name="source_filter_empty_screen">Δε βρέθηκε εγκατεστημένη πηγή</string> <string name="source_filter_empty_screen">Δε βρέθηκε εγκατεστημένη πηγή</string>
<string name="action_sort_unread_count">Αριθμός μη αναγνωσμένων</string> <string name="action_sort_unread_count">Αριθμός μη αναγνωσμένων</string>
<string name="action_sort_last_manga_update">Τελευταία ενημέρωση manga</string> <string name="action_sort_last_manga_update">Τελευταίος έλεγχος ενημέρωσης</string>
<string name="source_empty_screen">Δε βρέθηκε πηγή</string> <string name="source_empty_screen">Δε βρέθηκε πηγή</string>
<string name="split_tall_images">Διαχωρισμός ψηλών εικόνων</string> <string name="split_tall_images">Διαχωρισμός ψηλών εικόνων</string>
<string name="split_tall_images_summary">Βελτιώνει την απόδοση του αναγνώστη</string> <string name="split_tall_images_summary">Βελτιώνει την απόδοση του αναγνώστη</string>
@ -728,7 +728,7 @@
<string name="pref_reset_user_agent_string">Επαναφορά προεπιλεγμένης συμβολοσειράς πράκτορα χρήστη</string> <string name="pref_reset_user_agent_string">Επαναφορά προεπιλεγμένης συμβολοσειράς πράκτορα χρήστη</string>
<string name="action_remove_everything">Καταργήστε τα πάντα</string> <string name="action_remove_everything">Καταργήστε τα πάντα</string>
<string name="loader_rar5_error">Η μορφή RARv5 δεν υποστηρίζεται</string> <string name="loader_rar5_error">Η μορφή RARv5 δεν υποστηρίζεται</string>
<string name="appwidget_updates_description">Δείτε τα πρόσφατα ενημερωμένα manga σας</string> <string name="appwidget_updates_description">Δείτε τις πρόσφατα ενημερωμένες καταχωρήσεις της βιβλιοθήκης σας</string>
<string name="appwidget_unavailable_locked">Το widget δεν είναι διαθέσιμο όταν είναι ενεργοποιημένο το κλείδωμα εφαρμογών</string> <string name="appwidget_unavailable_locked">Το widget δεν είναι διαθέσιμο όταν είναι ενεργοποιημένο το κλείδωμα εφαρμογών</string>
<string name="update_already_running">Εκτελείται ήδη μια ενημέρωση</string> <string name="update_already_running">Εκτελείται ήδη μια ενημέρωση</string>
<string name="error_user_agent_string_blank">Η συμβολοσειρά πράκτορα χρήστη δεν μπορεί να είναι κενή</string> <string name="error_user_agent_string_blank">Η συμβολοσειρά πράκτορα χρήστη δεν μπορεί να είναι κενή</string>
@ -766,4 +766,10 @@
<string name="invalid_location">Μη έγκυρη τοποθεσία: %s</string> <string name="invalid_location">Μη έγκυρη τοποθεσία: %s</string>
<string name="error_user_agent_string_invalid">Μη έγκυρη συμβολοσειρά πράκτορα χρήστη</string> <string name="error_user_agent_string_invalid">Μη έγκυρη συμβολοσειρά πράκτορα χρήστη</string>
<string name="updates_last_update_info_just_now">Μόλις τώρα</string> <string name="updates_last_update_info_just_now">Μόλις τώρα</string>
<string name="download_notifier_cache_renewal">Λήψεις ευρετηρίου</string>
<string name="channel_downloader_cache">Προσωρινή μνήμη λήψεων</string>
<string name="fdroid_warning">Οι εκδόσεις F-Droid δεν υποστηρίζονται επίσημα.
\nΠατήστε για να μάθετε περισσότερα.</string>
<string name="action_open_random_manga">Άνοιγμα τυχαίας καταχώρησης</string>
<string name="information_no_entries_found">Δε βρέθηκαν καταχωρήσεις σε αυτή την κατηγορία</string>
</resources> </resources>

View File

@ -184,7 +184,7 @@
<string name="update_check_look_for_updates">Buscando actualizaciones…</string> <string name="update_check_look_for_updates">Buscando actualizaciones…</string>
<!--UpdateCheck Notifications--> <!--UpdateCheck Notifications-->
<string name="update_check_notification_download_in_progress">Descargando…</string> <string name="update_check_notification_download_in_progress">Descargando…</string>
<string name="update_check_notification_download_complete">Toca aquí para instalar</string> <string name="update_check_notification_download_complete">Toca aquí para instalar la actualización</string>
<string name="update_check_notification_download_error">Error de descarga</string> <string name="update_check_notification_download_error">Error de descarga</string>
<string name="update_check_notification_update_available">¡Nueva versión disponible!</string> <string name="update_check_notification_update_available">¡Nueva versión disponible!</string>
<!--Content Description--> <!--Content Description-->
@ -340,7 +340,7 @@
<string name="action_webview_refresh">Actualizar</string> <string name="action_webview_refresh">Actualizar</string>
<string name="pref_category_library">Biblioteca</string> <string name="pref_category_library">Biblioteca</string>
<string name="ext_obsolete">Obsoleto</string> <string name="ext_obsolete">Obsoleto</string>
<string name="obsolete_extension_message">Esta extensión ya no está disponible.</string> <string name="obsolete_extension_message">Esta extensión ya no está disponible. Puede que no funcione bien y te cause problemas con el resto de la aplicación, te recomendamos desinstalarla.</string>
<string name="pref_date_format">Formato de fecha</string> <string name="pref_date_format">Formato de fecha</string>
<string name="pref_category_library_update">Actualización global</string> <string name="pref_category_library_update">Actualización global</string>
<string name="logout">Cerrar sesión</string> <string name="logout">Cerrar sesión</string>
@ -810,4 +810,9 @@
<string name="invalid_location">Ubicación incorrecta: %s</string> <string name="invalid_location">Ubicación incorrecta: %s</string>
<string name="updates_last_update_info_just_now">Ahora mismo</string> <string name="updates_last_update_info_just_now">Ahora mismo</string>
<string name="error_user_agent_string_invalid">El nombre de agente de usuario no vale</string> <string name="error_user_agent_string_invalid">El nombre de agente de usuario no vale</string>
<string name="download_notifier_cache_renewal">Reindexando descargas</string>
<string name="channel_downloader_cache">Caché de descargas</string>
<string name="action_open_random_manga">Abrir manga al azar</string>
<string name="information_no_entries_found">No se ha encontrado ningún manga en esta categoría</string>
<string name="fdroid_warning">No damos soporte oficial a las versiones de F-Droid. Toca para más información.</string>
</resources> </resources>

View File

@ -672,4 +672,12 @@
\n \n
\nJarraitu nahi duzu\?</string> \nJarraitu nahi duzu\?</string>
<string name="action_filter_started">Hasia</string> <string name="action_filter_started">Hasia</string>
<string name="ext_info_version">Bertsioa</string>
<string name="multi_lang">Multi</string>
<string name="delete_category">Ezabatu kategoria</string>
<string name="action_close">Itxi</string>
<string name="pref_app_language">App hizkuntza</string>
<string name="battery_not_low">Bateria baxu ez dagoenean</string>
<string name="ext_info_language">Hizkuntza</string>
<string name="action_search_hint">Bilatu…</string>
</resources> </resources>

View File

@ -94,8 +94,8 @@
<string name="label_recent_manga">Nakaraan</string> <string name="label_recent_manga">Nakaraan</string>
<string name="label_recent_updates">Bago</string> <string name="label_recent_updates">Bago</string>
<string name="label_library">Aklatan</string> <string name="label_library">Aklatan</string>
<string name="label_download_queue">Dina-download</string> <string name="label_download_queue">Mga Dina-download</string>
<string name="label_settings">Pagsasaayos</string> <string name="label_settings">Mga Settings</string>
<string name="label_more">Higit pa</string> <string name="label_more">Higit pa</string>
<string name="name">Pangalan</string> <string name="name">Pangalan</string>
<plurals name="lock_after_mins"> <plurals name="lock_after_mins">
@ -105,7 +105,7 @@
<string name="lock_never">Hindi</string> <string name="lock_never">Hindi</string>
<string name="lock_always">Palagi</string> <string name="lock_always">Palagi</string>
<string name="lock_when_idle">Isara kung nakatambay</string> <string name="lock_when_idle">Isara kung nakatambay</string>
<string name="lock_with_biometrics">Kailangang i-unlock</string> <string name="lock_with_biometrics">I-lock gamit ang biometrics</string>
<string name="pref_manage_notifications">Pamahalaan ang mga abiso</string> <string name="pref_manage_notifications">Pamahalaan ang mga abiso</string>
<string name="pref_category_security">Seguridad</string> <string name="pref_category_security">Seguridad</string>
<string name="pref_confirm_exit">Kumpirmahing aalis</string> <string name="pref_confirm_exit">Kumpirmahing aalis</string>
@ -198,7 +198,7 @@
<string name="pref_cutout_short">Ipakita ang laman sa cutout area</string> <string name="pref_cutout_short">Ipakita ang laman sa cutout area</string>
<string name="pref_fullscreen">Naka-fullscreen</string> <string name="pref_fullscreen">Naka-fullscreen</string>
<string name="unofficial_extension_message">Wala sa opisyal na listahan ng mga extension ng Tachiyomi ang extension na ito.</string> <string name="unofficial_extension_message">Wala sa opisyal na listahan ng mga extension ng Tachiyomi ang extension na ito.</string>
<string name="obsolete_extension_message">Wala na\'ng ganitong extension.</string> <string name="obsolete_extension_message">Hindi na available ang extension na ito. Maaaring hindi ito gumana nang maayos at maaaring magdulot ng mga isyu sa app. Inirerekomenda ang pag-uninstall nito.</string>
<string name="untrusted_extension_message">Pinirmahan ang extension na ito gamit ang isang kaduda-dudang certificate at hindi muna pinagana. <string name="untrusted_extension_message">Pinirmahan ang extension na ito gamit ang isang kaduda-dudang certificate at hindi muna pinagana.
\n \n
\nMaaaring mabasa ng isang kaduda-dudang extension ang kahit anong credentials sa pag-login na nakatago sa Tachiyomi o di kaya nama\'y magsimula ng delikadong code. \nMaaaring mabasa ng isang kaduda-dudang extension ang kahit anong credentials sa pag-login na nakatago sa Tachiyomi o di kaya nama\'y magsimula ng delikadong code.
@ -246,7 +246,7 @@
<string name="secure_screen">Bantayan ang screen</string> <string name="secure_screen">Bantayan ang screen</string>
<string name="pref_read_with_tapping_inverted">Baligtarin ang mga tap zone</string> <string name="pref_read_with_tapping_inverted">Baligtarin ang mga tap zone</string>
<string name="backup_restore_missing_sources">Nawawalang (mga) source:</string> <string name="backup_restore_missing_sources">Nawawalang (mga) source:</string>
<string name="invalid_backup_file_missing_manga">Walang manga ang backup.</string> <string name="invalid_backup_file_missing_manga">Ang backup ay hindi naglalaman ng anumang mga entry sa library.</string>
<string name="invalid_backup_file">Invalid na backup</string> <string name="invalid_backup_file">Invalid na backup</string>
<string name="backup_created">Nai-backup na</string> <string name="backup_created">Nai-backup na</string>
<string name="pref_backup_slots">Dami ng backup</string> <string name="pref_backup_slots">Dami ng backup</string>
@ -259,7 +259,7 @@
<string name="pref_create_backup">Mag-backup</string> <string name="pref_create_backup">Mag-backup</string>
<string name="pref_search_pinned_sources_only">Isama lang ang mga naka-pin</string> <string name="pref_search_pinned_sources_only">Isama lang ang mga naka-pin</string>
<string name="pref_enable_automatic_extension_updates">Tumingin ng mga update sa extension</string> <string name="pref_enable_automatic_extension_updates">Tumingin ng mga update sa extension</string>
<string name="tracking_info">Isahang pagsabay para ma-update ang bilang ng nabasang kabanata sa mga tracker. Isaayos ang pag-track sa mga ito sa Pagta-track na makikita sa screen nila.</string> <string name="tracking_info">One-way na pag-sync upang i-update ang pag-unlad ng kabanata sa mga serbisyo sa pagsubaybay. I-set up ang pagsubaybay para sa mga indibidwal na entry mula sa kanilang button sa pagsubaybay.</string>
<string name="services">Mga Serbisyo</string> <string name="services">Mga Serbisyo</string>
<string name="pref_auto_update_manga_sync">I-update ang progress pagkabasa</string> <string name="pref_auto_update_manga_sync">I-update ang progress pagkabasa</string>
<string name="pref_download_new">I-download ang mga bago</string> <string name="pref_download_new">I-download ang mga bago</string>
@ -286,19 +286,19 @@
<string name="information_webview_required">Kailangan ng Tachiyomi ang WebView</string> <string name="information_webview_required">Kailangan ng Tachiyomi ang WebView</string>
<string name="information_cloudflare_bypass_failure">Bigong ma-bypass ang Cloudflare</string> <string name="information_cloudflare_bypass_failure">Bigong ma-bypass ang Cloudflare</string>
<plurals name="update_check_notification_ext_updates"> <plurals name="update_check_notification_ext_updates">
<item quantity="one">Merong update sa extension</item> <item quantity="one">Ang extension ay available upang i-update</item>
<item quantity="other">May %d (na) update sa extension</item> <item quantity="other">Ang mga %d (na) extension ay available upang i-update</item>
</plurals> </plurals>
<string name="update_check_notification_update_available">May bagong bersyon!</string> <string name="update_check_notification_update_available">May bagong bersyon!</string>
<string name="update_check_notification_download_error">Nagka-error sa pag-download</string> <string name="update_check_notification_download_error">Nagka-error sa pag-download</string>
<string name="update_check_notification_download_complete">Pindutin para ma-install</string> <string name="update_check_notification_download_complete">Pindutin upang ma-install ang update</string>
<string name="update_check_notification_download_in_progress">Dina-download…</string> <string name="update_check_notification_download_in_progress">Dina-download…</string>
<string name="update_check_look_for_updates">Naghahanap ng mga update…</string> <string name="update_check_look_for_updates">Naghahanap ng mga update…</string>
<string name="update_check_no_new_updates">Walang bagong update</string> <string name="update_check_no_new_updates">Walang bagong update</string>
<string name="update_check_confirm">I-download</string> <string name="update_check_confirm">I-download</string>
<string name="file_select_backup">Pumili ng backup</string> <string name="file_select_backup">Pumili ng backup</string>
<string name="file_select_cover">Pumili ng cover</string> <string name="file_select_cover">Pumili ng cover</string>
<string name="notification_first_add_to_library">Mangyaring idagdag muna ang manga sa Aklatan bago gawin ito</string> <string name="notification_first_add_to_library">Maaaring ilagay ang entry sa iyong library bago ito gawin</string>
<string name="notification_cover_update_failed">Bigong mapalitan ang cover</string> <string name="notification_cover_update_failed">Bigong mapalitan ang cover</string>
<plurals name="notification_chapters_multiple_and_more"> <plurals name="notification_chapters_multiple_and_more">
<item quantity="one">Mga kabanata %1$s at isa pa</item> <item quantity="one">Mga kabanata %1$s at isa pa</item>
@ -345,7 +345,7 @@
<string name="set_as_cover">Gawing cover</string> <string name="set_as_cover">Gawing cover</string>
<string name="custom_filter">Pinili kong filter</string> <string name="custom_filter">Pinili kong filter</string>
<string name="picture_saved">Na-save na ang larawan</string> <string name="picture_saved">Na-save na ang larawan</string>
<string name="dialog_with_checkbox_reset">I-reset ang lahat ng kabanata ng manga na ito</string> <string name="dialog_with_checkbox_reset">I-reset ang lahat ng chapters sa entry na ito</string>
<string name="dialog_with_checkbox_remove_description">Tatanggalin nito ang petsa ng pagbasa sa kabanatang ito. Sigurado ka ba\?</string> <string name="dialog_with_checkbox_remove_description">Tatanggalin nito ang petsa ng pagbasa sa kabanatang ito. Sigurado ka ba\?</string>
<string name="snack_categories_deleted">Binura na ang mga kategorya</string> <string name="snack_categories_deleted">Binura na ang mga kategorya</string>
<string name="error_category_exists">Mayroong kapangalan ang kategoryang ito!</string> <string name="error_category_exists">Mayroong kapangalan ang kategoryang ito!</string>
@ -382,7 +382,7 @@
<string name="chapter_error">Nagka-error</string> <string name="chapter_error">Nagka-error</string>
<string name="chapter_downloading_progress">Dina-download (%1$d/%2$d)</string> <string name="chapter_downloading_progress">Dina-download (%1$d/%2$d)</string>
<string name="display_mode_chapter">Kabanata %1$s</string> <string name="display_mode_chapter">Kabanata %1$s</string>
<string name="snack_add_to_library">Idagdag ang manga sa Aklatan\?</string> <string name="snack_add_to_library">Idagdag sa library\?</string>
<string name="source_not_installed">Di naka-install ang source: %1$s</string> <string name="source_not_installed">Di naka-install ang source: %1$s</string>
<string name="copied_to_clipboard">Kinopya sa clipboard: <string name="copied_to_clipboard">Kinopya sa clipboard:
\n%1$s</string> \n%1$s</string>
@ -431,9 +431,9 @@
<string name="login_title">Mag-login sa %1$s</string> <string name="login_title">Mag-login sa %1$s</string>
<plurals name="download_queue_summary"> <plurals name="download_queue_summary">
<item quantity="one">%1$s na lang</item> <item quantity="one">%1$s na lang</item>
<item quantity="other">%1$s na lang</item> <item quantity="other">%1$s pa ang nasa queue</item>
</plurals> </plurals>
<string name="downloaded_only_summary">I-filter ang lahat ng manga sa Aklatan</string> <string name="downloaded_only_summary">I-filter ang lahat ng mga entry sa iyong library</string>
<string name="label_downloaded_only">Mga na-download lang</string> <string name="label_downloaded_only">Mga na-download lang</string>
<string name="pref_acra_summary">Nakatutulong sa pag-ayos sa mga bug. Walang sensitibong data ang ipapadala</string> <string name="pref_acra_summary">Nakatutulong sa pag-ayos sa mga bug. Walang sensitibong data ang ipapadala</string>
<string name="pref_enable_acra">Ipadala ang mga ulat ng pag-crash</string> <string name="pref_enable_acra">Ipadala ang mga ulat ng pag-crash</string>
@ -451,8 +451,8 @@
<string name="pref_refresh_library_tracking">Sariwain ang pagta-track</string> <string name="pref_refresh_library_tracking">Sariwain ang pagta-track</string>
<string name="pref_refresh_library_covers">Sariwain ang mga cover sa Aklatan</string> <string name="pref_refresh_library_covers">Sariwain ang mga cover sa Aklatan</string>
<string name="clear_database_completed">Binura na</string> <string name="clear_database_completed">Binura na</string>
<string name="clear_database_confirmation">Sigurado ka ba talaga\? Mawawala ang lahat ng mga nabasang kabanata at bahagdang nabasa ng mga manga na wala sa Aklatan</string> <string name="clear_database_confirmation">Sigurado ka ba\? Mga basa na chapters at mga progress sa mga entry ng non-library ay mawawala</string>
<string name="pref_clear_database_summary">Burahin ang nakaraan ng mga manga na hindi nakalagay sa Aklatan mo</string> <string name="pref_clear_database_summary">Burahin ang history para sa mga entry na hindi naka-save sa iyong library</string>
<string name="pref_clear_database">Linisin ang database</string> <string name="pref_clear_database">Linisin ang database</string>
<string name="cache_delete_error">Nagka-error habang nililinis</string> <string name="cache_delete_error">Nagka-error habang nililinis</string>
<string name="cache_deleted">Nalinis na ang cache. Binura ang %1$d (na) file</string> <string name="cache_deleted">Nalinis na ang cache. Binura ang %1$d (na) file</string>
@ -493,18 +493,18 @@
<string name="ext_nsfw_warning">Posibleng may NSFW (18+) content ang mga source mula sa extension na ito</string> <string name="ext_nsfw_warning">Posibleng may NSFW (18+) content ang mga source mula sa extension na ito</string>
<string name="ext_nsfw_short">18+</string> <string name="ext_nsfw_short">18+</string>
<plurals name="missing_chapters_warning"> <plurals name="missing_chapters_warning">
<item quantity="one">Lalaktawan ang %d kabanata, siguro baka wala sa source ito, o baka na-filter ito</item> <item quantity="one">Nilaktawan ang %d na chapter, maaaring wala ang pinagmulan nito o na-filter na ito</item>
<item quantity="other">Lalaktawan ang %d (na) kabanata, siguro baka sa source ang mga ito, o baka na-filter sila</item> <item quantity="other">Lalaktawan ang %d na mga chapter, maaaring wala ang pinagmulan nito o na-filter na ito</item>
</plurals> </plurals>
<string name="no_chapters_error">Walang nakitang kabanata</string> <string name="no_chapters_error">Walang nakitang kabanata</string>
<string name="confirm_set_chapter_settings">Gusto mo bang i-save at ipagpaubaya ang pagsasaayos na ito\?</string> <string name="confirm_set_chapter_settings">Gusto mo bang i-save at ipagpaubaya ang pagsasaayos na ito\?</string>
<string name="set_chapter_settings_as_default">Ipagpaubaya</string> <string name="set_chapter_settings_as_default">Ipagpaubaya</string>
<string name="chapter_settings_updated">Ini-update na ang Ipagpaubaya</string> <string name="chapter_settings_updated">Ini-update na ang Ipagpaubaya</string>
<string name="share_page_info">%1$s: %2$s, pahina %3$d</string> <string name="share_page_info">%1$s: %2$s, pahina %3$d</string>
<string name="also_set_chapter_settings_for_library">Gawin din sa lahat ng mga manga sa Aklatan ko</string> <string name="also_set_chapter_settings_for_library">Gawin din sa lahat ng mga entry sa aking library</string>
<string name="chapter_settings">Pagsasaayos ng Kabanata</string> <string name="chapter_settings">Pagsasaayos ng Kabanata</string>
<string name="downloaded_chapters">Mga naka-download na kabanata</string> <string name="downloaded_chapters">Mga naka-download na kabanata</string>
<string name="manga_from_library">Manga mula sa Aklatan</string> <string name="manga_from_library">Galing sa library</string>
<string name="action_search_settings">Maghanap</string> <string name="action_search_settings">Maghanap</string>
<string name="pref_incognito_mode_summary">Hinihinto ang pagtala sa nakaraan</string> <string name="pref_incognito_mode_summary">Hinihinto ang pagtala sa nakaraan</string>
<string name="pref_incognito_mode">Nakatago</string> <string name="pref_incognito_mode">Nakatago</string>
@ -542,7 +542,7 @@
\n \n
\nKailangan mong i-install muli ang mga nawawalang extension at mag-login muli sa mga tracker pagkatapos para magamit ang mga ito.</string> \nKailangan mong i-install muli ang mga nawawalang extension at mag-login muli sa mga tracker pagkatapos para magamit ang mga ito.</string>
<string name="pref_dns_over_https">DNS kesa HTTPS (DoH)</string> <string name="pref_dns_over_https">DNS kesa HTTPS (DoH)</string>
<string name="pref_download_new_categories_details">Hindi ida-download ang mga manga na nasa di-kasamang kategorya kahit na nasa kasamang kategorya ang mga ito.</string> <string name="pref_download_new_categories_details">Ang mga entry sa mga ibinukod na kategorya ay hindi mada-download kahit na sila ay kasama rin sa mga kategoryang kasama.</string>
<string name="pref_category_auto_download">Kusang pag-download</string> <string name="pref_category_auto_download">Kusang pag-download</string>
<string name="nav_zone_right">Kanan</string> <string name="nav_zone_right">Kanan</string>
<string name="nav_zone_left">Kaliwa</string> <string name="nav_zone_left">Kaliwa</string>
@ -553,7 +553,7 @@
<string name="exclude">Di-kasama: %s</string> <string name="exclude">Di-kasama: %s</string>
<string name="include">Kasama: %s</string> <string name="include">Kasama: %s</string>
<string name="none">Wala</string> <string name="none">Wala</string>
<string name="pref_library_update_categories_details">Di ia-update ang mga manga na nasa mga kategoryang di-kasama kahit na nasa kasamang kategorya rin ito.</string> <string name="pref_library_update_categories_details">Ang mga entry sa mga ibinukod na kategorya ay hindi maa-update kahit na sila ay kasama rin sa mga kategoryang kasama.</string>
<string name="action_sort_chapter_fetch_date">Petsa kinuha</string> <string name="action_sort_chapter_fetch_date">Petsa kinuha</string>
<string name="action_show_errors">Pindutin para makita ang detalye</string> <string name="action_show_errors">Pindutin para makita ang detalye</string>
<string name="update_check_eol">Di na suportado ang bersyong ito ng Android</string> <string name="update_check_eol">Di na suportado ang bersyong ito ng Android</string>
@ -584,7 +584,7 @@
<string name="manga_cover">Cover</string> <string name="manga_cover">Cover</string>
<string name="off">Patayin</string> <string name="off">Patayin</string>
<string name="on">Buksan</string> <string name="on">Buksan</string>
<string name="action_display_local_badge">Lokal na manga</string> <string name="action_display_local_badge">Lokal na pinanggagalingan</string>
<string name="categorized_display_settings">Pagpapakita kada kategorya</string> <string name="categorized_display_settings">Pagpapakita kada kategorya</string>
<string name="information_empty_category_dialog">Wala ka pang kategorya.</string> <string name="information_empty_category_dialog">Wala ka pang kategorya.</string>
<string name="tracking_guide">Gabay sa pag-track</string> <string name="tracking_guide">Gabay sa pag-track</string>
@ -593,7 +593,7 @@
<string name="tracker_komga_warning">Gumagana lang ang tracker na ito gamit ang source ng Komga.</string> <string name="tracker_komga_warning">Gumagana lang ang tracker na ito gamit ang source ng Komga.</string>
<string name="about_dont_kill_my_app">May dagdag na mga restriksyon sa app ang ilang mga modelo ng phone na pumapatay sa mga serbisyong nasa likuran. May impormasyon sa site na ito para maayos ang naturang problema.</string> <string name="about_dont_kill_my_app">May dagdag na mga restriksyon sa app ang ilang mga modelo ng phone na pumapatay sa mga serbisyong nasa likuran. May impormasyon sa site na ito para maayos ang naturang problema.</string>
<string name="restore_miui_warning">Maaaring hindi gumana nang maayos ang pag-backup/pag-restore kung nakasara ang MIUI optimization.</string> <string name="restore_miui_warning">Maaaring hindi gumana nang maayos ang pag-backup/pag-restore kung nakasara ang MIUI optimization.</string>
<string name="enhanced_tracking_info">Mga serbisyong nagbibigay ng pinahusay na tampok para sa mga piling source. Kusang ita-track ang manga kapag dinagdag ito sa Aklatan mo.</string> <string name="enhanced_tracking_info">Mga serbisyong nagbibigay ng mga pinahusay na feature para sa mga partikular na source. Awtomatikong sinusubaybayan ang mga entry kapag idinagdag sa iyong library.</string>
<string name="enhanced_services">Pinahusay na serbisyo</string> <string name="enhanced_services">Pinahusay na serbisyo</string>
<string name="theme_midnightdusk">Hatinggabi</string> <string name="theme_midnightdusk">Hatinggabi</string>
<string name="theme_greenapple">Berdeng Mansanas</string> <string name="theme_greenapple">Berdeng Mansanas</string>
@ -616,7 +616,7 @@
<string name="pref_relative_format">Pagpepetsa</string> <string name="pref_relative_format">Pagpepetsa</string>
<plurals name="relative_time"> <plurals name="relative_time">
<item quantity="one">Kahapon</item> <item quantity="one">Kahapon</item>
<item quantity="other">%1$d (na) araw ang nakalipas</item> <item quantity="other">%1$d na mga araw ang lumipas</item>
</plurals> </plurals>
<string name="recently">Kamakailan</string> <string name="recently">Kamakailan</string>
<string name="relative_time_today">Ngayon</string> <string name="relative_time_today">Ngayon</string>
@ -636,7 +636,7 @@
<string name="ext_installer_legacy">Legasiya</string> <string name="ext_installer_legacy">Legasiya</string>
<string name="ext_installer_pref">Taga-install</string> <string name="ext_installer_pref">Taga-install</string>
<string name="ext_install_service_notif">Ini-install ang extension…</string> <string name="ext_install_service_notif">Ini-install ang extension…</string>
<string name="action_sort_count">Dami ng manga</string> <string name="action_sort_count">Dami ng mga entry</string>
<string name="pref_verbose_logging">Verbose na pagla-log</string> <string name="pref_verbose_logging">Verbose na pagla-log</string>
<string name="pref_verbose_logging_summary">Mag-print ng mga verbose na log sa log ng sistema (dagdag-pasanin sa app)</string> <string name="pref_verbose_logging_summary">Mag-print ng mga verbose na log sa log ng sistema (dagdag-pasanin sa app)</string>
<string name="notification_size_warning">Babala: Nakasasama ang mga malalaking update sa source at maaaring humantong sa mabagal na update at pagtaas ng paggamit sa baterya. Pindutin para matuto pa.</string> <string name="notification_size_warning">Babala: Nakasasama ang mga malalaking update sa source at maaaring humantong sa mabagal na update at pagtaas ng paggamit sa baterya. Pindutin para matuto pa.</string>
@ -649,7 +649,7 @@
<string name="pref_library_update_show_tab_badge">Ipakita ang bilang ng di pa nabasa sa Bago</string> <string name="pref_library_update_show_tab_badge">Ipakita ang bilang ng di pa nabasa sa Bago</string>
<string name="channel_app_updates">Mga update sa app</string> <string name="channel_app_updates">Mga update sa app</string>
<string name="ext_update_all">I-update lahat</string> <string name="ext_update_all">I-update lahat</string>
<string name="clear_database_source_item_count">May %1$d (na) manga sa database na wala sa Aklatan</string> <string name="clear_database_source_item_count">May %1$d na mga entry na non-library sa database</string>
<string name="pref_auto_clear_chapter_cache">Linisin ang cache ng kabanata pagkasara</string> <string name="pref_auto_clear_chapter_cache">Linisin ang cache ng kabanata pagkasara</string>
<string name="database_clean">Walang malilinis</string> <string name="database_clean">Walang malilinis</string>
<string name="extension_api_error">Bigong makuha ang mga extension</string> <string name="extension_api_error">Bigong makuha ang mga extension</string>
@ -663,7 +663,7 @@
<string name="cancelled">Kinansela</string> <string name="cancelled">Kinansela</string>
<string name="action_faq_and_guides">Mga Madalas Itanong at Gabay</string> <string name="action_faq_and_guides">Mga Madalas Itanong at Gabay</string>
<string name="webtoon_side_padding_5">5%</string> <string name="webtoon_side_padding_5">5%</string>
<string name="action_show_manga">Ipakita ang manga</string> <string name="action_show_manga">Ipakita ang mga entry</string>
<string name="action_filter_started">Nasimulan</string> <string name="action_filter_started">Nasimulan</string>
<string name="action_display_cover_only_grid">Pabalat lang</string> <string name="action_display_cover_only_grid">Pabalat lang</string>
<string name="skipped_reason_completed">Nilaktawan dahil tapos na ang serye</string> <string name="skipped_reason_completed">Nilaktawan dahil tapos na ang serye</string>
@ -694,7 +694,7 @@
<string name="pref_duplicate_pinned_sources_summary">Ulitin ang pag-pin sa mga source sa kani-kanilang mga wika</string> <string name="pref_duplicate_pinned_sources_summary">Ulitin ang pag-pin sa mga source sa kani-kanilang mga wika</string>
<string name="source_filter_empty_screen">Walang nakitang naka-install na source</string> <string name="source_filter_empty_screen">Walang nakitang naka-install na source</string>
<string name="source_empty_screen">Walang nakitang source</string> <string name="source_empty_screen">Walang nakitang source</string>
<string name="action_sort_last_manga_update">Huling update sa manga</string> <string name="action_sort_last_manga_update">Huling update ng pag-tsek</string>
<string name="action_sort_unread_count">Dami ng di pa nabasa</string> <string name="action_sort_unread_count">Dami ng di pa nabasa</string>
<string name="split_tall_images">Hatiin ang mga matatangkad na larawan</string> <string name="split_tall_images">Hatiin ang mga matatangkad na larawan</string>
<string name="split_tall_images_summary">Pinapahusay ang performance ng reader</string> <string name="split_tall_images_summary">Pinapahusay ang performance ng reader</string>
@ -728,7 +728,7 @@
<string name="pref_user_agent_string">Default na string ng user agent</string> <string name="pref_user_agent_string">Default na string ng user agent</string>
<string name="action_remove_everything">Burahin lahat</string> <string name="action_remove_everything">Burahin lahat</string>
<string name="loader_rar5_error">Di suportado ang format na RARv5</string> <string name="loader_rar5_error">Di suportado ang format na RARv5</string>
<string name="appwidget_updates_description">Tingnan ang mga kamakailang na-update na manga mo</string> <string name="appwidget_updates_description">Tingnan ang iyong kamakailang na-update na mga entry sa library</string>
<string name="appwidget_unavailable_locked">Di available ang widget kapag nakabukas ang lock</string> <string name="appwidget_unavailable_locked">Di available ang widget kapag nakabukas ang lock</string>
<string name="update_already_running">May ina-update sa ngayon</string> <string name="update_already_running">May ina-update sa ngayon</string>
<string name="error_user_agent_string_blank">Hindi dapat blangko ang user agent string</string> <string name="error_user_agent_string_blank">Hindi dapat blangko ang user agent string</string>
@ -738,12 +738,12 @@
<string name="download_ahead_info">Gagana lang sa mga entry sa aklatan at kung naka-download na ang kasalukuyang kabanata pati ang susunod</string> <string name="download_ahead_info">Gagana lang sa mga entry sa aklatan at kung naka-download na ang kasalukuyang kabanata pati ang susunod</string>
<string name="are_you_sure">Sigurado ka ba\?</string> <string name="are_you_sure">Sigurado ka ba\?</string>
<plurals name="next_unread_chapters"> <plurals name="next_unread_chapters">
<item quantity="one">Susunod na kabanatang di pa nabasa</item> <item quantity="one">Susunod ang hindi pa nababasa na chapter</item>
<item quantity="other">Susunod na %d (na) kabanatang di pa nabasa</item> <item quantity="other">Susunod ang mga %d na hindi pa nababasa na mga chapter</item>
</plurals> </plurals>
<string name="multi_lang">Marami</string> <string name="multi_lang">Marami</string>
<string name="remove_manga">Tatanggalin mo na ang \"%s\" mula sa iyong aklatan</string> <string name="remove_manga">Tatanggalin mo na ang \"%s\" mula sa iyong aklatan</string>
<string name="updates_last_update_info">Huling na-update ang Aklatan noong: %1$s</string> <string name="updates_last_update_info">Huling update ng Library: %s</string>
<string name="pref_long_strip_split">Hatiin ang mga matatangkad na larawan (BETA)</string> <string name="pref_long_strip_split">Hatiin ang mga matatangkad na larawan (BETA)</string>
<string name="popular">Sikat</string> <string name="popular">Sikat</string>
<string name="missing_storage_permission">Hindi binigay ang mga permiso sa storage</string> <string name="missing_storage_permission">Hindi binigay ang mga permiso sa storage</string>
@ -763,7 +763,13 @@
<string name="pref_general_summary">Wika ng App, mga abiso</string> <string name="pref_general_summary">Wika ng App, mga abiso</string>
<string name="crash_screen_restart_application">Buksan muli ang app</string> <string name="crash_screen_restart_application">Buksan muli ang app</string>
<string name="invalid_location">Hindi wastong lokasyon: %s</string> <string name="invalid_location">Hindi wastong lokasyon: %s</string>
<string name="unknown_title">Di tiyak na pamagat</string> <string name="unknown_title">Hindi alam ang Pamagat</string>
<string name="error_user_agent_string_invalid">Hindi wastong user agent</string> <string name="error_user_agent_string_invalid">Hindi wasto ang string ng user agent</string>
<string name="updates_last_update_info_just_now">Ngayon lang</string> <string name="updates_last_update_info_just_now">Ngayon lang</string>
<string name="download_notifier_cache_renewal">Pag-index ng mga pag-download</string>
<string name="channel_downloader_cache">Cache ng mga download</string>
<string name="information_no_entries_found">Walang mga entry ang nahanap sa kategoryang ito</string>
<string name="action_open_random_manga">Buksan ang random na entry</string>
<string name="fdroid_warning">Ang mga build ng F-Droid ay hindi opisyal na sinusuportahan.
\nI-tap para matuto pa.</string>
</resources> </resources>

View File

@ -376,7 +376,7 @@
<string name="pref_library_columns">פריטים לשורה</string> <string name="pref_library_columns">פריטים לשורה</string>
<string name="theme_system">עקוב אחר המערכת</string> <string name="theme_system">עקוב אחר המערכת</string>
<string name="pref_library_update_refresh_metadata_summary">בדוק אם יש כריכה ופרטים חדשים בעת עדכון הספרייה</string> <string name="pref_library_update_refresh_metadata_summary">בדוק אם יש כריכה ופרטים חדשים בעת עדכון הספרייה</string>
<string name="action_sort_count">סה\"כ מאנגה</string> <string name="action_sort_count">סה\"כ פריטים</string>
<string name="action_search_settings">הגדרות חיפוש</string> <string name="action_search_settings">הגדרות חיפוש</string>
<string name="action_select_inverse">בחר את ההפך</string> <string name="action_select_inverse">בחר את ההפך</string>
<string name="action_display_language_badge">שפה</string> <string name="action_display_language_badge">שפה</string>
@ -408,7 +408,7 @@
<string name="action_migrate">העברה</string> <string name="action_migrate">העברה</string>
<string name="action_display_comfortable_grid">רשת נוחה</string> <string name="action_display_comfortable_grid">רשת נוחה</string>
<string name="action_display_unread_badge">פרקים שלא נקראו</string> <string name="action_display_unread_badge">פרקים שלא נקראו</string>
<string name="action_display_local_badge">מנגה מקומית</string> <string name="action_display_local_badge">מקור מקומי</string>
<string name="theme_tealturquoise">טורקיז צהבהב</string> <string name="theme_tealturquoise">טורקיז צהבהב</string>
<string name="pref_dark_theme_pure_black">מצב שחור כהה טהור</string> <string name="pref_dark_theme_pure_black">מצב שחור כהה טהור</string>
<string name="theme_greenapple">ירוק תפוח</string> <string name="theme_greenapple">ירוק תפוח</string>
@ -425,7 +425,7 @@
<string name="action_sort_chapter_fetch_date">תאריך האחזור של הפרקים</string> <string name="action_sort_chapter_fetch_date">תאריך האחזור של הפרקים</string>
<string name="theme_monet">דינמי</string> <string name="theme_monet">דינמי</string>
<string name="pref_library_update_categories_details">מנגה הנמצאת בקטגוריית מנועי העדכונים לא תעודכן גם אם היא נכללת בקטגורייה אחרת שכן מתעדכנת.</string> <string name="pref_library_update_categories_details">מנגה הנמצאת בקטגוריית מנועי העדכונים לא תעודכן גם אם היא נכללת בקטגורייה אחרת שכן מתעדכנת.</string>
<string name="action_show_manga">הראה מנגה</string> <string name="action_show_manga">הראה פריט</string>
<string name="action_filter_started">התחיל</string> <string name="action_filter_started">התחיל</string>
<string name="pref_update_only_completely_read">עם פרק(ים) שלא נקרא(ו)</string> <string name="pref_update_only_completely_read">עם פרק(ים) שלא נקרא(ו)</string>
<string name="action_move_to_top_all_for_series">העבר סדרה לראש</string> <string name="action_move_to_top_all_for_series">העבר סדרה לראש</string>
@ -626,7 +626,7 @@
<string name="learn_more">לחץ כדי ללמוד עוד</string> <string name="learn_more">לחץ כדי ללמוד עוד</string>
<string name="pref_clear_history">נקה היסטוריה</string> <string name="pref_clear_history">נקה היסטוריה</string>
<string name="pref_reset_viewer_flags_error">נכשל איפוס הגדרות מצב הקריאה</string> <string name="pref_reset_viewer_flags_error">נכשל איפוס הגדרות מצב הקריאה</string>
<string name="action_sort_last_manga_update">עדכון המנגה האחרון</string> <string name="action_sort_last_manga_update">עדכון אחרון</string>
<string name="action_sort_unread_count">לא נקראו</string> <string name="action_sort_unread_count">לא נקראו</string>
<string name="battery_not_low">אין קצת סוללה</string> <string name="battery_not_low">אין קצת סוללה</string>
<string name="categorized_display_settings">הגדרות מיון ותצוגה לכל קטגוריה בנפרד</string> <string name="categorized_display_settings">הגדרות מיון ותצוגה לכל קטגוריה בנפרד</string>
@ -685,4 +685,8 @@
<string name="pref_dual_page_invert_summary">אם המקום של הפיצול עמוד לא תואם לכיוון הקריאה</string> <string name="pref_dual_page_invert_summary">אם המקום של הפיצול עמוד לא תואם לכיוון הקריאה</string>
<string name="tapping_inverted_none">כלום</string> <string name="tapping_inverted_none">כלום</string>
<string name="pref_viewer_nav">איזורי נגיעה</string> <string name="pref_viewer_nav">איזורי נגיעה</string>
<string name="loader_rar5_error">הפורמט RARv5 לא נתמך</string>
<string name="action_search_hint">חיפוש…</string>
<string name="action_open_random_manga">פתיחת פריט אקראי</string>
<string name="getting_started_guide">מדריך למתחיל</string>
</resources> </resources>

View File

@ -523,7 +523,7 @@
<string name="action_display_show_number_of_items">Tampilkan jumlah item</string> <string name="action_display_show_number_of_items">Tampilkan jumlah item</string>
<string name="none">Kosong</string> <string name="none">Kosong</string>
<string name="action_show_errors">Ketuk untuk melihat detail</string> <string name="action_show_errors">Ketuk untuk melihat detail</string>
<string name="action_sort_chapter_fetch_date">Tanggal pengambilan bab</string> <string name="action_sort_chapter_fetch_date">Tanggal bab dimasukkan</string>
<string name="pref_reader_actions">Aksi</string> <string name="pref_reader_actions">Aksi</string>
<string name="nav_zone_right">Kanan</string> <string name="nav_zone_right">Kanan</string>
<string name="nav_zone_left">Kiri</string> <string name="nav_zone_left">Kiri</string>
@ -682,7 +682,7 @@
<string name="pref_duplicate_pinned_sources_summary">Ulang sumber ditandai pada kelompok bahasa masing-masing</string> <string name="pref_duplicate_pinned_sources_summary">Ulang sumber ditandai pada kelompok bahasa masing-masing</string>
<string name="source_filter_empty_screen">Sumber yang diinstal tidak ditemukan</string> <string name="source_filter_empty_screen">Sumber yang diinstal tidak ditemukan</string>
<string name="source_empty_screen">Tidak ada sumber yang ditemukan</string> <string name="source_empty_screen">Tidak ada sumber yang ditemukan</string>
<string name="action_sort_last_manga_update">Pembaruan manga terakhir</string> <string name="action_sort_last_manga_update">Pembaruan terakhir</string>
<string name="action_sort_unread_count">Jumlah belum dibaca</string> <string name="action_sort_unread_count">Jumlah belum dibaca</string>
<string name="split_tall_images">Membagi gambar panjang</string> <string name="split_tall_images">Membagi gambar panjang</string>
<string name="split_tall_images_summary">Meningkatkan kinerja pembaca</string> <string name="split_tall_images_summary">Meningkatkan kinerja pembaca</string>

View File

@ -214,7 +214,7 @@
<string name="update_check_look_for_updates">Ricerca aggiornamenti…</string> <string name="update_check_look_for_updates">Ricerca aggiornamenti…</string>
<!--UpdateCheck Notifications--> <!--UpdateCheck Notifications-->
<string name="update_check_notification_download_in_progress">Download in corso…</string> <string name="update_check_notification_download_in_progress">Download in corso…</string>
<string name="update_check_notification_download_complete">Tocca per installare</string> <string name="update_check_notification_download_complete">Tocca per installare l\'aggiornamento</string>
<string name="update_check_notification_download_error">Errore di scaricamento</string> <string name="update_check_notification_download_error">Errore di scaricamento</string>
<string name="update_check_notification_update_available">Nuova versione disponibile!</string> <string name="update_check_notification_update_available">Nuova versione disponibile!</string>
<!--Content Description--> <!--Content Description-->
@ -341,7 +341,7 @@
<string name="action_webview_refresh">Aggiorna</string> <string name="action_webview_refresh">Aggiorna</string>
<string name="pref_category_library">Libreria</string> <string name="pref_category_library">Libreria</string>
<string name="ext_obsolete">Obsoleta</string> <string name="ext_obsolete">Obsoleta</string>
<string name="obsolete_extension_message">Questa estensione non è più disponibile.</string> <string name="obsolete_extension_message">Questa estensione non è più disponibile. Potrebbe non funzionare correttamente e causare problemi con l\'app. Si consiglia di disinstallarla.</string>
<string name="pref_date_format">Formato data</string> <string name="pref_date_format">Formato data</string>
<string name="pref_category_library_update">Aggiornamenti globali</string> <string name="pref_category_library_update">Aggiornamenti globali</string>
<string name="logout_title">Disconnettersi da %1$s\?</string> <string name="logout_title">Disconnettersi da %1$s\?</string>
@ -812,4 +812,10 @@
<string name="unknown_title">Titolo sconosciuto</string> <string name="unknown_title">Titolo sconosciuto</string>
<string name="error_user_agent_string_invalid">Stringa «user agent» non valida</string> <string name="error_user_agent_string_invalid">Stringa «user agent» non valida</string>
<string name="updates_last_update_info_just_now">Proprio adesso</string> <string name="updates_last_update_info_just_now">Proprio adesso</string>
<string name="channel_downloader_cache">Cache di download</string>
<string name="download_notifier_cache_renewal">Indicizzazione dei download</string>
<string name="action_open_random_manga">Apri una voce casuale</string>
<string name="information_no_entries_found">Nessuna voce trovata in questa categoria</string>
<string name="fdroid_warning">Le versioni F-Droid non sono ufficialmente supportate.
\nToccare per saperne di più.</string>
</resources> </resources>

View File

@ -264,7 +264,7 @@
<string name="update_check_no_new_updates">新しいバージョンがありません</string> <string name="update_check_no_new_updates">新しいバージョンがありません</string>
<string name="update_check_look_for_updates">アップデートを確認中…</string> <string name="update_check_look_for_updates">アップデートを確認中…</string>
<string name="update_check_notification_download_in_progress">ダウンロード中…</string> <string name="update_check_notification_download_in_progress">ダウンロード中…</string>
<string name="update_check_notification_download_complete">タップでインストール</string> <string name="update_check_notification_download_complete">タップでアップデートをインストール</string>
<string name="update_check_notification_download_error">ダウンロード中にエラー発生</string> <string name="update_check_notification_download_error">ダウンロード中にエラー発生</string>
<string name="update_check_notification_update_available">アップデート利用可能!</string> <string name="update_check_notification_update_available">アップデート利用可能!</string>
<string name="information_no_downloads">ダウンロードがありません</string> <string name="information_no_downloads">ダウンロードがありません</string>
@ -302,7 +302,7 @@
<string name="action_webview_refresh">再ロード</string> <string name="action_webview_refresh">再ロード</string>
<string name="pref_category_library">ライブラリ</string> <string name="pref_category_library">ライブラリ</string>
<string name="ext_obsolete">廃止済み</string> <string name="ext_obsolete">廃止済み</string>
<string name="obsolete_extension_message">この拡張機能は利用不可になりました</string> <string name="obsolete_extension_message">この拡張機能は利用不可になったため、正常に機能しなかったり、アプリでエラーを起こしたりする恐れがあります。アンインストールすることをお勧めします</string>
<string name="pref_date_format">日付形式</string> <string name="pref_date_format">日付形式</string>
<string name="pref_category_library_update">グローバルアップデート</string> <string name="pref_category_library_update">グローバルアップデート</string>
<string name="logout_title">%1$sからログアウトしますか</string> <string name="logout_title">%1$sからログアウトしますか</string>
@ -431,7 +431,7 @@
<string name="notification_chapters_single_and_more">第%1$s章とその他%2$d章</string> <string name="notification_chapters_single_and_more">第%1$s章とその他%2$d章</string>
<string name="notification_chapters_multiple">第%1$s章</string> <string name="notification_chapters_multiple">第%1$s章</string>
<plurals name="notification_new_chapters_summary"> <plurals name="notification_new_chapters_summary">
<item quantity="other">%d件のタイトル</item> <item quantity="other">%d件の項目</item>
</plurals> </plurals>
<string name="recent_manga_time">第%1$s章 - %2$s</string> <string name="recent_manga_time">第%1$s章 - %2$s</string>
<string name="page_list_empty_error">ページが見つかりません</string> <string name="page_list_empty_error">ページが見つかりません</string>
@ -642,7 +642,7 @@
<string name="database_clean">消去できるものはありませんでした</string> <string name="database_clean">消去できるものはありませんでした</string>
<string name="extension_api_error">拡張機能リストを取得できませんでした</string> <string name="extension_api_error">拡張機能リストを取得できませんでした</string>
<string name="privacy_policy">プライバシーポリシー</string> <string name="privacy_policy">プライバシーポリシー</string>
<string name="pref_library_update_manga_restriction">タイトル更新のスキップ</string> <string name="pref_library_update_manga_restriction">項目更新のスキップ</string>
<string name="pref_update_only_completely_read">未読の章あり</string> <string name="pref_update_only_completely_read">未読の章あり</string>
<string name="library_errors_help">ライブラリ更新エラーの修正については、%1$sをご覧ください</string> <string name="library_errors_help">ライブラリ更新エラーの修正については、%1$sをご覧ください</string>
<string name="save_chapter_as_cbz">CBZアーカイブとして保存</string> <string name="save_chapter_as_cbz">CBZアーカイブとして保存</string>
@ -753,4 +753,10 @@
<string name="pref_advanced_summary">クラッシュ ログのダンプ、バッテリーの最適化</string> <string name="pref_advanced_summary">クラッシュ ログのダンプ、バッテリーの最適化</string>
<string name="pref_appearance_summary">テーマ、日付と時刻の形式</string> <string name="pref_appearance_summary">テーマ、日付と時刻の形式</string>
<string name="pref_library_summary">カテゴリ、グローバル アップデート</string> <string name="pref_library_summary">カテゴリ、グローバル アップデート</string>
<string name="channel_downloader_cache">ダウンロード キャッシュ</string>
<string name="action_open_random_manga">おまかせ閲覧</string>
<string name="information_no_entries_found">このカテゴリに項目がありません</string>
<string name="download_notifier_cache_renewal">ダウンロードのインデックスを作成しています</string>
<string name="fdroid_warning">F-Droidビルドは正式にサポートされていません。
\n詳細はタップしてご覧ください。</string>
</resources> </resources>

View File

@ -225,7 +225,7 @@
<string name="update_check_no_new_updates">새로운 업데이트 없음</string> <string name="update_check_no_new_updates">새로운 업데이트 없음</string>
<string name="update_check_look_for_updates">업데이트를 찾는중…</string> <string name="update_check_look_for_updates">업데이트를 찾는중…</string>
<string name="update_check_notification_download_in_progress">다운로드 중…</string> <string name="update_check_notification_download_in_progress">다운로드 중…</string>
<string name="update_check_notification_download_complete">탭하여 설치</string> <string name="update_check_notification_download_complete">탭하여 업데이트 설치</string>
<string name="update_check_notification_download_error">다운로드 오류</string> <string name="update_check_notification_download_error">다운로드 오류</string>
<string name="update_check_notification_update_available">업데이트 이용 가능!</string> <string name="update_check_notification_update_available">업데이트 이용 가능!</string>
<string name="information_no_downloads">다운로드 없음</string> <string name="information_no_downloads">다운로드 없음</string>
@ -272,7 +272,7 @@
<string name="custom_download">다운로드 할 회차 직접 입력</string> <string name="custom_download">다운로드 할 회차 직접 입력</string>
<string name="download_custom">사용자 정의</string> <string name="download_custom">사용자 정의</string>
<string name="reading">읽는 중</string> <string name="reading">읽는 중</string>
<string name="completed"></string> <string name="completed"></string>
<string name="dropped">포기함</string> <string name="dropped">포기함</string>
<string name="on_hold">일시중지중</string> <string name="on_hold">일시중지중</string>
<string name="plan_to_read">계획중</string> <string name="plan_to_read">계획중</string>
@ -546,7 +546,7 @@
<string name="information_webview_required">Tachiyomi를 사용하려면 WebView가 필요합니다</string> <string name="information_webview_required">Tachiyomi를 사용하려면 WebView가 필요합니다</string>
<string name="information_webview_outdated">호환성을 위해 WebView 어플리케이션을 업데이트 해 주세요</string> <string name="information_webview_outdated">호환성을 위해 WebView 어플리케이션을 업데이트 해 주세요</string>
<string name="pref_navigate_pan">탭 하여 넓은 이미지 이동</string> <string name="pref_navigate_pan">탭 하여 넓은 이미지 이동</string>
<string name="obsolete_extension_message">이 확장기능은 더이상 이용이 불가능합니다.</string> <string name="obsolete_extension_message">이 확장 프로그램은 더 이상 사용할 수 없습니다. 제대로 작동하지 않을 수 있으며 앱에 문제가 발생할 수 있습니다. 제거하는 것이 좋습니다.</string>
<string name="ext_install_service_notif">확장기능 설치 중…</string> <string name="ext_install_service_notif">확장기능 설치 중…</string>
<string name="ext_installer_legacy">레거시</string> <string name="ext_installer_legacy">레거시</string>
<string name="webtoon_side_padding_10">10%</string> <string name="webtoon_side_padding_10">10%</string>
@ -753,4 +753,10 @@
<string name="invalid_location">잘못된 위치: %s</string> <string name="invalid_location">잘못된 위치: %s</string>
<string name="error_user_agent_string_invalid">잘못된 사용자 에이전트 문자열</string> <string name="error_user_agent_string_invalid">잘못된 사용자 에이전트 문자열</string>
<string name="updates_last_update_info_just_now">방금</string> <string name="updates_last_update_info_just_now">방금</string>
<string name="channel_downloader_cache">캐시 다운로드</string>
<string name="download_notifier_cache_renewal">다운로드 인덱싱</string>
<string name="action_open_random_manga">무작위 항목 열기</string>
<string name="information_no_entries_found">이 카테고리에 항목이 없습니다</string>
<string name="fdroid_warning">F-Droid 빌드는 공식적으로 지원되지 않습니다.
\n자세히 알아보려면 탭하세요.</string>
</resources> </resources>

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