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 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
- 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

View File

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

View File

@ -33,7 +33,7 @@ body:
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).
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
- label: I will fill out all of the requested information in this form.
required: true

View File

@ -27,8 +27,8 @@ android {
applicationId = "eu.kanade.tachiyomi"
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
versionCode = 90
versionName = "0.14.1"
versionCode = 91
versionName = "0.14.2"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@ -113,7 +113,6 @@ android {
"META-INF/README.md",
"META-INF/NOTICE",
"META-INF/*.kotlin_module",
"META-INF/*.version",
))
}
@ -176,8 +175,6 @@ dependencies {
implementation(compose.accompanist.webview)
implementation(compose.accompanist.swiperefresh)
implementation(compose.accompanist.flowlayout)
implementation(compose.accompanist.pager.core)
implementation(compose.accompanist.pager.indicators)
implementation(compose.accompanist.permissions)
implementation(androidx.paging.runtime)
@ -190,6 +187,8 @@ dependencies {
implementation(libs.sqldelight.android.paging)
implementation(kotlinx.reflect)
implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines)
// AndroidX libraries
@ -298,6 +297,11 @@ androidComponents {
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 {

View File

@ -34,7 +34,7 @@ import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.history.interactor.DeleteAllHistory
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.RemoveHistoryByMangaId
import eu.kanade.domain.history.interactor.UpsertHistory
@ -94,7 +94,7 @@ class DomainModule : InjektModule {
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetManga(get()) }
addFactory { GetNextChapter(get(), get(), get(), get()) }
addFactory { GetNextUnreadChapters(get(), get(), get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(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,
) {
suspend fun await(manga: Manga, sourceId: Long): Manga {
val localManga = getManga(manga.url, sourceId)
suspend fun await(manga: Manga): Manga {
val localManga = getManga(manga.url, manga.source)
return when {
localManga == null -> {
val id = insertManga(manga.copy(source = sourceId))
val id = insertManga(manga)
manga.copy(id = id!!)
}
!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(
url = url,
title = title,
@ -244,6 +244,7 @@ fun SManga.toDomainManga(): Manga {
thumbnailUrl = thumbnail_url,
updateStrategy = update_strategy,
initialized = initialized,
source = sourceId,
)
}

View File

@ -4,12 +4,9 @@ import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.util.DisplayMetrics
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold
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.tachiyomi.R
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
private fun DetailsHeader(
extension: Extension,
@ -380,15 +362,14 @@ private fun SourceSwitchPreference(
) {
val context = LocalContext.current
PreferenceRow(
TextPreferenceWidget(
modifier = modifier,
title = if (source.labelAsName) {
source.source.toString()
} else {
LocaleHelper.getSourceDisplayName(source.source.lang, context)
},
onClick = { onClickSource(source.source.id) },
action = {
widget = {
Row(
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.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
@ -11,10 +10,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.AppBar
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.PreferenceRow
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper
@ -42,15 +41,13 @@ fun ExtensionFilterScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
else -> {
SourceFilterContent(
contentPadding = contentPadding,
state = presenter,
onClickLang = {
presenter.toggleLanguage(it)
},
)
}
else -> ExtensionFilterContent(
contentPadding = contentPadding,
state = presenter,
onClickLang = {
presenter.toggleLanguage(it)
},
)
}
}
LaunchedEffect(Unit) {
@ -65,40 +62,24 @@ fun ExtensionFilterScreen(
}
@Composable
private fun SourceFilterContent(
private fun ExtensionFilterContent(
contentPadding: PaddingValues,
state: ExtensionFilterState,
onClickLang: (String) -> Unit,
) {
LazyColumn(
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
items(
items = state.items,
) { model ->
ExtensionFilterItem(
val lang = model.lang
SwitchPreferenceWidget(
modifier = Modifier.animateItemPlacement(),
lang = model.lang,
enabled = model.enabled,
onClickItem = onClickLang,
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
checked = model.enabled,
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.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.manga.components.BaseMangaListItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
@ -72,7 +72,7 @@ private fun MigrateMangaContent(
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
ScrollbarLazyColumn(
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
items(state.items) { manga ->

View File

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

View File

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

View File

@ -1,13 +1,10 @@
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.filled.ViewList
import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
@ -15,14 +12,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.text.input.ImeAction
import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.presentation.browse.BrowseSourceState
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar
@ -42,59 +37,21 @@ fun BrowseSourceToolbar(
onSearch: (String) -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
if (state.searchQuery == null) {
BrowseSourceRegularToolbar(
title = if (state.isUserQuery) state.currentFilter.query else source?.name.orEmpty(),
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,
)
}
}
// Avoid capturing unstable source in actions lambda
val title = source?.name
val isLocalSource = source is LocalSource
@Composable
fun BrowseSourceRegularToolbar(
title: String,
isLocalSource: Boolean,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,
onSearchClick: () -> Unit,
onWebViewClick: () -> Unit,
onHelpClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) {
AppBar(
SearchToolbar(
navigateUp = navigateUp,
title = title,
titleContent = { AppBarTitle(title) },
searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
onSearch = onSearch,
onClickCloseSearch = navigateUp,
actions = {
var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions(
actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_search),
icon = Icons.Outlined.Search,
onClick = onSearchClick,
),
AppBar.Action(
title = stringResource(R.string.action_display_mode),
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)) },
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
}
RadioMenuItem(
text = { Text(text = stringResource(R.string.action_display_grid)) },
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
}
RadioMenuItem(
text = { Text(text = stringResource(R.string.action_display_list)) },
isChecked = displayMode == LibraryDisplayMode.List,
) {
selectingDisplayMode = false
onDisplayModeChange(LibraryDisplayMode.List)
}
}
@ -142,34 +102,3 @@ fun BrowseSourceRegularToolbar(
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@ -22,7 +24,7 @@ fun CategoryCreateDialog(
onDismissRequest: () -> Unit,
onCreate: (String) -> Unit,
) {
val (name, onNameChange) = remember { mutableStateOf("") }
var name by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
AlertDialog(
@ -48,7 +50,7 @@ fun CategoryCreateDialog(
modifier = Modifier
.focusRequester(focusRequester),
value = name,
onValueChange = onNameChange,
onValueChange = { name = it },
label = {
Text(text = stringResource(R.string.name))
},
@ -70,7 +72,7 @@ fun CategoryRenameDialog(
onRename: (String) -> Unit,
category: Category,
) {
val (name, onNameChange) = remember { mutableStateOf(category.name) }
var name by remember { mutableStateOf(category.name) }
val focusRequester = remember { FocusRequester() }
AlertDialog(
@ -96,7 +98,7 @@ fun CategoryRenameDialog(
modifier = Modifier
.focusRequester(focusRequester),
value = name,
onValueChange = onNameChange,
onValueChange = { name = it },
label = {
Text(text = stringResource(R.string.name))
},

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package eu.kanade.presentation.components
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@ -13,6 +14,23 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
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
fun AppStateBanners(
downloadedOnlyMode: Boolean,

View File

@ -26,6 +26,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
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.stringResource
import androidx.compose.ui.semantics.Role
@ -43,26 +45,41 @@ enum class ChapterDownloadAction {
@Composable
fun ChapterDownloadIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int,
onClick: (ChapterDownloadAction) -> Unit,
) {
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(
enabled = enabled,
modifier = modifier,
downloadState = downloadState,
downloadProgressProvider = downloadProgressProvider,
onClick = onClick,
)
Download.State.DOWNLOADED -> DownloadedIndicator(modifier = modifier, onClick = onClick)
Download.State.ERROR -> ErrorIndicator(modifier = modifier, onClick = onClick)
Download.State.DOWNLOADED -> DownloadedIndicator(
enabled = enabled,
modifier = modifier,
onClick = onClick,
)
Download.State.ERROR -> ErrorIndicator(
enabled = enabled,
modifier = modifier,
onClick = onClick,
)
}
}
@Composable
private fun NotDownloadedIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit,
) {
@ -70,6 +87,7 @@ private fun NotDownloadedIndicator(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
.commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
onClick = { onClick(ChapterDownloadAction.START) },
)
@ -87,6 +105,7 @@ private fun NotDownloadedIndicator(
@Composable
private fun DownloadingIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
downloadState: Download.State,
downloadProgressProvider: () -> Int,
@ -97,6 +116,7 @@ private fun DownloadingIndicator(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
.commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
onClick = { isMenuExpanded = true },
),
@ -158,6 +178,7 @@ private fun DownloadingIndicator(
@Composable
private fun DownloadedIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit,
) {
@ -166,6 +187,7 @@ private fun DownloadedIndicator(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
.commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.DELETE) },
onClick = { isMenuExpanded = true },
),
@ -191,6 +213,7 @@ private fun DownloadedIndicator(
@Composable
private fun ErrorIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: (ChapterDownloadAction) -> Unit,
) {
@ -198,6 +221,7 @@ private fun ErrorIndicator(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
.commonClickable(
enabled = enabled,
onLongClick = { onClick(ChapterDownloadAction.START) },
onClick = { onClick(ChapterDownloadAction.START) },
),
@ -213,11 +237,18 @@ private fun ErrorIndicator(
}
private fun Modifier.commonClickable(
enabled: Boolean,
onLongClick: () -> Unit,
onClick: () -> Unit,
) = composed {
val haptic = LocalHapticFeedback.current
this.combinedClickable(
onLongClick = onLongClick,
enabled = enabled,
onLongClick = {
onLongClick()
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onClick = onClick,
role = Role.Button,
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
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.sizeIn
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.RadioButtonUnchecked
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
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.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties
import eu.kanade.tachiyomi.R
import me.saket.cascade.CascadeColumnScope
import me.saket.cascade.CascadeDropdownMenu
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
@Composable
@ -22,6 +31,7 @@ fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(8.dp, (-56).dp),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit,
) {
@ -29,7 +39,7 @@ fun DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
offset = DpOffset(8.dp, (-56).dp),
offset = offset,
properties = properties,
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.util.secondaryItemAlpha
import eu.kanade.tachiyomi.R
@ -100,9 +101,9 @@ fun EmptyScreen(
modifier = modifier.fillMaxSize(),
) { measurables, constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val facePlaceable = measurables.first { it.layoutId == "face" }
val facePlaceable = measurables.fastFirstOrNull { it.layoutId == "face" }!!
.measure(looseConstraints)
val actionsPlaceable = measurables.firstOrNull { it.layoutId == "actions" }
val actionsPlaceable = measurables.fastFirstOrNull { it.layoutId == "actions" }
?.measure(looseConstraints)
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.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -170,6 +173,7 @@ private fun RowScope.Button(
toConfirm: Boolean,
onLongClick: () -> Unit,
onClick: () -> Unit,
content: (@Composable () -> Unit)? = null,
) {
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
Column(
@ -201,6 +205,7 @@ private fun RowScope.Button(
style = MaterialTheme.typography.labelSmall,
)
}
content?.invoke()
}
}
@ -211,7 +216,7 @@ fun LibraryBottomActionMenu(
onChangeCategoryClicked: (() -> Unit)?,
onMarkAsReadClicked: (() -> Unit)?,
onMarkAsUnreadClicked: (() -> Unit)?,
onDownloadClicked: (() -> Unit)?,
onDownloadClicked: ((DownloadAction) -> Unit)?,
onDeleteClicked: (() -> Unit)?,
) {
AnimatedVisibility(
@ -270,13 +275,22 @@ fun LibraryBottomActionMenu(
)
}
if (onDownloadClicked != null) {
var downloadExpanded by remember { mutableStateOf(false) }
Button(
title = stringResource(R.string.action_download),
icon = Icons.Outlined.Download,
toConfirm = confirm[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) {
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,
fontSize = fontSize,
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.WindowInsets
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.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
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.LayoutDirection
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>.
@ -59,6 +66,7 @@ import androidx.compose.ui.unit.dp
* * Pass scroll behavior to top bar by default
* * Remove height constraint for expanded app bar
* * 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 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
* matching content color for [containerColor], or to the current [LocalContentColor] if
* [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
* 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
@ -89,6 +100,7 @@ fun Scaffold(
floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit,
) {
androidx.compose.material3.Surface(
@ -104,6 +116,7 @@ fun Scaffold(
bottomBar = bottomBar,
content = content,
snackbar = snackbarHost,
contentWindowInsets = contentWindowInsets,
fab = floatingActionButton,
)
}
@ -129,6 +142,7 @@ private fun ScaffoldLayout(
content: @Composable (PaddingValues) -> Unit,
snackbar: @Composable () -> Unit,
fab: @Composable () -> Unit,
contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit,
) {
SubcomposeLayout { constraints ->
@ -143,37 +157,51 @@ private fun ScaffoldLayout(
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
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)
}
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)
}
val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0
val snackbarWidth = snackbarPlaceables.maxByOrNull { it.width }?.width ?: 0
val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 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 =
subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable ->
measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 }
subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
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 fabWidth = fabPlaceables.maxByOrNull { it.width }!!.width
val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) {
// 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) {
if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth
layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset
} else {
FabSpacing.roundToPx()
FabSpacing.roundToPx() + leftInset
}
} else {
(layoutWidth - fabWidth) / 2
leftInset + ((insetLayoutWidth - fabWidth) / 2)
}
FabPlacement(
@ -190,75 +218,63 @@ private fun ScaffoldLayout(
LocalFabPlacement provides fabPlacement,
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 {
if (bottomBarHeight == 0) {
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()
}
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
}
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
} else {
0
}
/**
* Tachiyomi: Also take account of fab height when providing inner padding
*/
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
val insets = WindowInsets.Companion.safeDrawing
.asPaddingValues(this@SubcomposeLayout)
val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp
val bottomBarHeightPx = bottomBarHeight ?: 0
val innerPadding = PaddingValues(
top =
if (topBarHeight == 0) {
if (topBarPlaceables.isEmpty()) {
insets.calculateTopPadding()
} else {
topBarHeight.toDp()
},
bottom =
(
if (bottomBarHeight == 0) {
insets.calculateBottomPadding()
} else {
bottomBarHeight.toDp()
}
) + fabHeight.toDp(),
start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection),
bottom = // Tachiyomi: Also take account of fab height when providing inner padding
if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
max(insets.calculateBottomPadding(), fabOffsetDp)
} else {
max(bottomBarHeightPx.toDp(), fabOffsetDp)
},
start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
)
content(innerPadding)
}.map { it.measure(looseConstraints) }
}.fastMap { it.measure(looseConstraints) }
// Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.forEach {
bodyContentPlaceables.fastForEach {
it.place(0, 0)
}
topBarPlaceables.forEach {
topBarPlaceables.fastForEach {
it.place(0, 0)
}
snackbarPlaceables.forEach {
snackbarPlaceables.fastForEach {
it.place(
(layoutWidth - snackbarWidth) / 2,
snackbarLeft,
layoutHeight - snackbarOffsetFromBottom,
)
}
// The bottom bar is always at the bottom of the layout
bottomBarPlaceables.forEach {
it.place(0, layoutHeight - bottomBarHeight)
bottomBarPlaceables.fastForEach {
it.place(0, layoutHeight - (bottomBarHeight ?: 0))
}
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
fabPlacement?.let { placement ->
fabPlaceables.forEach {
it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
}
fabPlaceables.fastForEach {
it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,19 +1,13 @@
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.outlined.DeleteSweep
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBarScrollBehavior
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.text.input.ImeAction
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
@ -26,54 +20,12 @@ fun HistoryToolbar(
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
if (state.searchQuery == null) {
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),
SearchToolbar(
titleContent = { AppBarTitle(stringResource(R.string.history)) },
searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
actions = {
IconButton(onClick = onClickSearch) {
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
}
IconButton(onClick = onClickDelete) {
IconButton(onClick = { state.dialog = HistoryPresenter.Dialog.DeleteAll }) {
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.getValue
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.util.fastAll
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.display
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.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@ -29,14 +33,17 @@ fun LibraryScreen(
onChangeCategoryClicked: () -> Unit,
onMarkAsReadClicked: () -> Unit,
onMarkAsUnreadClicked: () -> Unit,
onDownloadClicked: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
onDeleteClicked: () -> Unit,
onClickUnselectAll: () -> Unit,
onClickSelectAll: () -> Unit,
onClickInvertSelection: () -> Unit,
onClickFilter: () -> Unit,
onClickRefresh: (Category?) -> Boolean,
onClickOpenRandomManga: () -> Unit,
) {
val haptic = LocalHapticFeedback.current
Scaffold(
topBar = { scrollBehavior ->
val title by presenter.getToolbarTitle()
@ -51,6 +58,7 @@ fun LibraryScreen(
onClickInvertSelection = onClickInvertSelection,
onClickFilter = onClickFilter,
onClickRefresh = { onClickRefresh(null) },
onClickOpenRandomManga = onClickOpenRandomManga,
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
)
},
@ -60,7 +68,7 @@ fun LibraryScreen(
onChangeCategoryClicked = onChangeCategoryClicked,
onMarkAsReadClicked = onMarkAsReadClicked,
onMarkAsUnreadClicked = onMarkAsUnreadClicked,
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.none { it.manga.isLocal() } },
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.fastAll { !it.manga.isLocal() } },
onDeleteClicked = onDeleteClicked,
)
},
@ -97,7 +105,10 @@ fun LibraryScreen(
onChangeCurrentPage = { presenter.activeCategory = it },
onMangaClicked = onMangaClicked,
onToggleSelection = { presenter.toggleSelection(it) },
onToggleRangeSelection = { presenter.toggleRangeSelection(it) },
onToggleRangeSelection = {
presenter.toggleRangeSelection(it)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onRefresh = onClickRefresh,
onGlobalSearchClicked = onGlobalSearchClicked,
getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) },

View File

@ -15,12 +15,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.rememberPagerState
import eu.kanade.presentation.library.LibraryState
import eu.kanade.tachiyomi.ui.library.LibraryItem
import kotlinx.coroutines.delay
@ -68,12 +68,13 @@ fun LibraryContent(
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
LibraryTabs(
state = pagerState,
categories = categories,
currentPageIndex = pagerState.currentPage,
showMangaCount = showMangaCount,
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
isDownloadOnly = isDownloadOnly,
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -39,7 +40,10 @@ fun LibraryList(
) {
item {
if (searchQuery.isNullOrEmpty().not()) {
TextButton(onClick = onGlobalSearchClicked) {
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onGlobalSearchClicked,
) {
Text(
text = stringResource(R.string.action_global_search_query, searchQuery!!),
modifier = Modifier.zIndex(99f),

View File

@ -10,11 +10,11 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.domain.library.model.LibraryDisplayMode
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
@Composable

View File

@ -1,56 +1,54 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
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 com.google.accompanist.pager.PagerState
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText
import kotlinx.coroutines.launch
@Composable
fun LibraryTabs(
state: PagerState,
categories: List<Category>,
currentPageIndex: Int,
showMangaCount: Boolean,
isDownloadOnly: Boolean,
isIncognitoMode: Boolean,
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
onTabItemClick: (Int) -> Unit,
) {
val scope = rememberCoroutineScope()
Column {
ScrollableTabRow(
selectedTabIndex = state.currentPage,
selectedTabIndex = currentPageIndex,
edgePadding = 0.dp,
indicator = { TabIndicator(it[state.currentPage]) },
indicator = { TabIndicator(it[currentPageIndex]) },
// TODO: use default when width is fixed upstream
// https://issuetracker.google.com/issues/242879624
divider = {},
) {
categories.forEachIndexed { index, category ->
val count by if (showMangaCount) {
getNumberOfMangaForCategory(category.id)
} else {
remember { mutableStateOf<Int?>(null) }
}
Tab(
selected = state.currentPage == index,
onClick = { scope.launch { state.animateScrollToPage(index) } },
selected = currentPageIndex == index,
onClick = { onTabItemClick(index) },
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.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.outlined.FilterList
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.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
@ -19,13 +16,11 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.library.LibraryState
@ -43,6 +38,7 @@ fun LibraryToolbar(
onClickInvertSelection: () -> Unit,
onClickFilter: () -> Unit,
onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) = when {
state.selectionMode -> LibrarySelectionToolbar(
@ -53,38 +49,16 @@ fun LibraryToolbar(
onClickSelectAll = onClickSelectAll,
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(
title = title,
hasFilters = state.hasActiveFilters,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
onClickSearch = { state.searchQuery = "" },
searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
onClickFilter = onClickFilter,
onClickRefresh = onClickRefresh,
onClickOpenRandomManga = onClickOpenRandomManga,
scrollBehavior = scrollBehavior,
)
}
@ -95,14 +69,15 @@ fun LibraryRegularToolbar(
hasFilters: Boolean,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onClickSearch: () -> Unit,
searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
onClickFilter: () -> Unit,
onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
AppBar(
SearchToolbar(
titleContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
@ -120,15 +95,29 @@ fun LibraryRegularToolbar(
}
}
},
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
actions = {
IconButton(onClick = onClickSearch) {
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
}
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
IconButton(onClick = onClickFilter) {
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,

View File

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

View File

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

View File

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

View File

@ -24,11 +24,14 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.updatePadding
@ -82,9 +85,15 @@ fun MangaCoverDialog(
}
if (onEditClick != null) {
Box {
val (expanded, onExpand) = remember { mutableStateOf(false) }
var expanded by remember { mutableStateOf(false) }
IconButton(
onClick = { if (isCustomCover) onExpand(true) else onEditClick(EditCoverAction.EDIT) },
onClick = {
if (isCustomCover) {
expanded = true
} else {
onEditClick(EditCoverAction.EDIT)
}
},
) {
Icon(
imageVector = Icons.Outlined.Edit,
@ -93,20 +102,21 @@ fun MangaCoverDialog(
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { onExpand(false) },
onDismissRequest = { expanded = false },
offset = DpOffset(8.dp, 0.dp),
) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_edit)) },
onClick = {
onEditClick(EditCoverAction.EDIT)
onExpand(false)
expanded = false
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_delete)) },
onClick = {
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.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.DropdownMenuItem
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.unit.dp
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.theme.active
import eu.kanade.tachiyomi.R
@ -101,53 +99,11 @@ fun MangaToolbar(
)
}
val onDismissRequest = { onDownloadExpanded(false) }
DropdownMenu(
DownloadDropdownMenu(
expanded = downloadExpanded,
onDismissRequest = onDismissRequest,
) {
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()
},
)
}
onDownloadClicked = onClickDownload,
)
}
}
@ -156,49 +112,39 @@ fun MangaToolbar(
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
}
if (onClickEditCategory != null && onClickMigrate != null) {
val (moreExpanded, onMoreExpanded) = remember { mutableStateOf(false) }
Box {
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,
) {
if (onClickEditCategory != null || onClickMigrate != null || onClickShare != null) {
OverflowMenu { closeMenu ->
if (onClickEditCategory != null) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_edit_categories)) },
onClick = {
onClickEditCategory()
onDismissRequest()
closeMenu()
},
)
}
if (onClickMigrate != null) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_migrate)) },
onClick = {
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(
containerColor = MaterialTheme.colorScheme
.surfaceColorAtElevation(3.dp)

View File

@ -1,5 +1,9 @@
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.material.icons.Icons
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.Divider
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.TextPreferenceWidget
import eu.kanade.tachiyomi.R
@ -32,6 +37,7 @@ import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@Composable
fun MoreScreen(
presenter: MorePresenter,
isFDroid: Boolean,
onClickDownloadQueue: () -> Unit,
onClickCategories: () -> Unit,
onClickBackupAndRestore: () -> Unit,
@ -43,8 +49,21 @@ fun MoreScreen(
ScrollbarLazyColumn(
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 {
LogoHeader()
}

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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.coroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel
@ -240,14 +241,14 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
fun selectAll() = mutableState.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 ->
if (state !is State.Ready) return@update state
state.copy(
selection = state.items
.map { it.id }
.fastMap { it.id }
.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.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastMap
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.domain.category.interactor.GetCategories
@ -204,8 +205,8 @@ class SettingsDownloadScreen : SearchableSettings {
itemLabel = { it.visualName },
onDismissRequest = { showDialog = false },
onValueChanged = { newIncluded, newExcluded ->
downloadNewChapterCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
downloadNewChapterCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
downloadNewChapterCategoriesPref.set(newIncluded.fastMap { it.id.toString() }.toSet())
downloadNewChapterCategoriesExcludePref.set(newExcluded.fastMap { it.id.toString() }.toSet())
showDialog = false
},
)

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastMap
import androidx.core.content.ContextCompat
import cafe.adriel.voyager.navigator.currentOrThrow
import com.bluelinelabs.conductor.Router
@ -124,9 +125,9 @@ class SettingsLibraryScreen : SearchableSettings {
// For default category
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)) +
allCategories.map { it.visualName(context) }
allCategories.fastMap { it.visualName(context) }
return Preference.PreferenceGroup(
title = stringResource(R.string.categories),

View File

@ -44,43 +44,42 @@ internal fun BasePreferenceWidget(
widget: @Composable (() -> Unit)? = null,
) {
val highlighted = LocalPreferenceHighlighted.current
Box(modifier = Modifier.highlightBackground(highlighted)) {
Row(
modifier = modifier
.sizeIn(minHeight = 56.dp)
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
Row(
modifier = modifier
.highlightBackground(highlighted)
.sizeIn(minHeight = 56.dp)
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth(),
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) {
Box(
modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp),
content = { icon() },
)
}
Column(
modifier = Modifier
.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() },
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() },
)
}
}
}

View File

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

View File

@ -12,8 +12,10 @@ import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -36,18 +38,18 @@ fun <T> ListPreferenceWidget(
entries: Map<out T, String>,
onValueChange: (T) -> Unit,
) {
val (isDialogShown, showDialog) = remember { mutableStateOf(false) }
var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget(
title = title,
subtitle = subtitle,
icon = icon,
onPreferenceClick = { showDialog(true) },
onPreferenceClick = { isDialogShown = true },
)
if (isDialogShown) {
AlertDialog(
onDismissRequest = { showDialog(false) },
onDismissRequest = { isDialogShown = false },
title = { Text(text = title) },
text = {
Box {
@ -61,7 +63,7 @@ fun <T> ListPreferenceWidget(
isSelected = isSelected,
onSelected = {
onValueChange(current.key!!)
showDialog(false)
isDialogShown = false
},
)
}
@ -72,7 +74,7 @@ fun <T> ListPreferenceWidget(
}
},
confirmButton = {
TextButton(onClick = { showDialog(false) }) {
TextButton(onClick = { isDialogShown = false }) {
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.TextButton
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.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -30,13 +32,13 @@ fun MultiSelectListPreferenceWidget(
values: Set<String>,
onValuesChange: (Set<String>) -> Unit,
) {
val (isDialogShown, showDialog) = remember { mutableStateOf(false) }
var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget(
title = preference.title,
subtitle = preference.subtitleProvider(values, preference.entries),
icon = preference.icon,
onPreferenceClick = { showDialog(true) },
onPreferenceClick = { isDialogShown = true },
)
if (isDialogShown) {
@ -46,7 +48,7 @@ fun MultiSelectListPreferenceWidget(
.toMutableStateList()
}
AlertDialog(
onDismissRequest = { showDialog(false) },
onDismissRequest = { isDialogShown = false },
title = { Text(text = preference.title) },
text = {
LazyColumn {
@ -91,14 +93,14 @@ fun MultiSelectListPreferenceWidget(
TextButton(
onClick = {
onValuesChange(selected.toMutableSet())
showDialog(false)
isDialogShown = false
},
) {
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { showDialog(false) }) {
TextButton(onClick = { isDialogShown = false }) {
Text(text = stringResource(R.string.action_cancel))
}
},

View File

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

View File

@ -2,11 +2,8 @@ package eu.kanade.presentation.updates
import androidx.activity.compose.BackHandler
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.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
@ -23,17 +20,17 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
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.ChapterDownloadAction
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.MangaBottomActionMenu
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.VerticalFastScroller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
@ -124,7 +121,6 @@ private fun UpdateScreenContent(
onClickCover: (UpdatesItem) -> Unit,
) {
val context = LocalContext.current
val updatesListState = rememberLazyListState()
val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) }
@ -143,34 +139,26 @@ private fun UpdateScreenContent(
enabled = presenter.selectionMode.not(),
indicatorPadding = contentPadding,
) {
VerticalFastScroller(
listState = updatesListState,
topContentPadding = contentPadding.calculateTopPadding(),
endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
LazyColumn(
modifier = Modifier.fillMaxHeight(),
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,
)
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,
)
}
}
@ -255,24 +243,24 @@ private fun UpdatesBottomBar(
modifier = Modifier.fillMaxWidth(),
onBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected, true)
}.takeIf { selected.any { !it.update.bookmark } },
}.takeIf { selected.fastAny { !it.update.bookmark } },
onRemoveBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected, false)
}.takeIf { selected.all { it.update.bookmark } },
}.takeIf { selected.fastAll { it.update.bookmark } },
onMarkAsReadClicked = {
onMultiMarkAsReadClicked(selected, true)
}.takeIf { selected.any { !it.update.read } },
}.takeIf { selected.fastAny { !it.update.read } },
onMarkAsUnreadClicked = {
onMultiMarkAsReadClicked(selected, false)
}.takeIf { selected.any { it.update.read } },
}.takeIf { selected.fastAny { it.update.read } },
onDownloadClicked = {
onDownloadChapter(selected, ChapterDownloadAction.START)
}.takeIf {
selected.any { it.downloadStateProvider() != Download.State.DOWNLOADED }
selected.fastAny { it.downloadStateProvider() != Download.State.DOWNLOADED }
},
onDeleteClicked = {
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
import android.text.format.DateUtils
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -28,7 +27,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity
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.util.ReadItemAlpha
import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
@ -135,6 +134,7 @@ fun LazyListScope.updatesUiItems(
onDownloadChapter = {
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
},
downloadIndicatorEnabled = selectionMode.not(),
downloadStateProvider = updatesItem.downloadStateProvider,
downloadProgressProvider = updatesItem.downloadProgressProvider,
)
@ -153,13 +153,14 @@ fun UpdatesUiItem(
onClickCover: () -> Unit,
onDownloadChapter: (ChapterDownloadAction) -> Unit,
// Download Indicator
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int,
) {
val haptic = LocalHapticFeedback.current
Row(
modifier = modifier
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.selectedBackground(selected)
.combinedClickable(
onClick = onClick,
onLongClick = {
@ -225,6 +226,7 @@ fun UpdatesUiItem(
}
}
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,

View File

@ -12,3 +12,4 @@ val verticalPadding = vertical
val topPaddingValues = PaddingValues(top = vertical)
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(
onLongClick: (() -> Unit)? = null,

View File

@ -244,6 +244,10 @@ class Downloader(
* @param autoStart whether to start the downloader after enqueing the chapters.
*/
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 wasEmpty = queue.isEmpty()
// 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>) {
// 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.
val dbChapters = chapters.map { it.toDbChapter() }
downloadManager.downloadChapters(manga, dbChapters, false)
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() }, false)
}
/**

View File

@ -271,11 +271,9 @@ class NotificationReceiver : BroadcastReceiver() {
*/
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
launchIO {
val manga = getManga.await(mangaId)
val manga = getManga.await(mangaId) ?: return@launchIO
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.parseAs
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 java.util.Date
import java.util.concurrent.TimeUnit
@ -38,7 +38,7 @@ class AppUpdateChecker {
// Check if latest version is different from current version
if (isNewVersion(it.version)) {
if (context.getInstallerPackageName() == "org.fdroid.fdroid") {
if (context.isInstalledFromFDroid()) {
AppUpdateResult.NewUpdateFdroidInstallation
} else {
AppUpdateResult.NewUpdate(it)

View File

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

View File

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

View File

@ -13,7 +13,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -36,7 +35,6 @@ class ExtensionFilterPresenter(
logcat(LogPriority.ERROR, exception)
_events.send(Event.FailedFetchingLanguages)
}
.stateIn(presenterScope)
.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.system.LocaleHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import rx.Observable
import uy.kohesive.injekt.Injekt
@ -116,7 +117,7 @@ class ExtensionsPresenter(
items
}
.stateIn(presenterScope)
.onStart { delay(500) } // Defer to avoid crashing on initial render
.collectLatest {
state.isLoading = false
state.items = it

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -260,6 +260,6 @@ open class GlobalSearchPresenter(
* @return a manga from the database.
*/
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.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.EmptyScreen
import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.Scaffold
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.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.util.lang.launchUI
import me.saket.cascade.CascadeDropdownMenu
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@ -147,69 +145,69 @@ class DownloadController :
navigateUp = router::popCurrentController,
actions = {
if (downloadList.isNotEmpty()) {
val (expanded, onExpanded) = remember { mutableStateOf(false) }
Box {
IconButton(onClick = { onExpanded(!expanded) }) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
)
}
CascadeDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpanded(false) },
) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_reorganize_by)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_order_by_upload_date)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_newest)) },
onClick = {
reorderQueue({ it.download.chapter.date_upload }, true)
onExpanded(false)
},
)
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)) },
onClick = {
reorderQueue({ it.download.chapter.chapter_number }, false)
onExpanded(false)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_desc)) },
onClick = {
reorderQueue({ it.download.chapter.chapter_number }, true)
onExpanded(false)
},
)
},
)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_cancel_all)) },
onClick = {
presenter.clearQueue(context)
onExpanded(false)
},
)
}
OverflowMenu { closeMenu ->
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_reorganize_by)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_order_by_upload_date)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_newest)) },
onClick = {
reorderQueue(
{ it.download.chapter.date_upload },
true,
)
closeMenu()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_oldest)) },
onClick = {
reorderQueue(
{ it.download.chapter.date_upload },
false,
)
closeMenu()
},
)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_order_by_chapter_number)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_asc)) },
onClick = {
reorderQueue(
{ it.download.chapter.chapter_number },
false,
)
closeMenu()
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_desc)) },
onClick = {
reorderQueue(
{ it.download.chapter.chapter_number },
true,
)
closeMenu()
},
)
},
)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_cancel_all)) },
onClick = {
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.DeleteLibraryMangaDialog
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.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
@ -43,6 +45,8 @@ class LibraryController(
@Composable
override fun ComposeContent() {
val context = LocalContext.current
val getMangaForCategory = presenter.getMangaForCategory(page = presenter.activeCategory)
LibraryScreen(
presenter = presenter,
onMangaClicked = ::openManga,
@ -52,7 +56,7 @@ class LibraryController(
onChangeCategoryClicked = ::showMangaCategoriesDialog,
onMarkAsReadClicked = { markReadStatus(true) },
onMarkAsUnreadClicked = { markReadStatus(false) },
onDownloadClicked = ::downloadUnreadChapters,
onDownloadClicked = ::runDownloadChapterAction,
onDeleteClicked = ::showDeleteMangaDialog,
onClickFilter = ::showSettingsSheet,
onClickRefresh = {
@ -60,6 +64,14 @@ class LibraryController(
context.toast(if (started) R.string.updating_category else R.string.update_already_running)
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) },
onClickSelectAll = { presenter.selectAll(presenter.activeCategory) },
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 -> {}
}
@ -208,9 +230,22 @@ class LibraryController(
}
}
private fun downloadUnreadChapters() {
val mangaList = presenter.selection.toList()
presenter.downloadUnreadChapters(mangaList.map { it.manga })
private fun runDownloadChapterAction(action: DownloadAction) {
val mangas = presenter.selection.map { it.manga }.toList()
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()
}

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.SetMangaCategories
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.model.toDbChapter
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.library.model.LibrarySort
import eu.kanade.domain.library.model.sort
@ -78,7 +78,7 @@ class LibraryPresenter(
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracksPerManga: GetTracksPerManga = 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 updateManga: UpdateManga = 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 amount the amount to queue or null to queue all
*/
fun downloadUnreadChapters(mangas: List<Manga>) {
fun downloadUnreadChapters(mangas: List<Manga>, amount: Int?) {
presenterScope.launchNonCancellable {
mangas.forEach { manga ->
val chapters = getChapterByMangaId.await(manga.id)
.filter { !it.read }
.map { it.toDbChapter() }
val chapters = getNextUnreadChapters.await(manga.id)
.filterNot { chapter ->
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
fun getMangaForCategory(page: Int): List<LibraryItem> {
val unfiltered = remember(categories, loadedManga) {
val unfiltered = remember(categories, loadedManga, page) {
val categoryId = categories.getOrNull(page)?.id ?: -1
loadedManga[categoryId] ?: emptyList()
}
@ -604,5 +613,6 @@ class LibraryPresenter(
sealed class Dialog {
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : 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.download.DownloadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
class MoreController :
FullComposeController<MorePresenter>(),
@ -19,6 +20,7 @@ class MoreController :
override fun ComposeContent() {
MoreScreen(
presenter = presenter,
isFDroid = activity?.isInstalledFromFDroid() ?: false,
onClickDownloadQueue = { router.pushController(DownloadController()) },
onClickCategories = { router.pushController(CategoryController()) },
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.toDbChapter
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.model.HistoryUpdate
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.service.TrackPreferences
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.download.DownloadManager
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.system.isOnline
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toInt
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
@ -74,7 +73,6 @@ import uy.kohesive.injekt.injectLazy
import java.util.Date
import java.util.concurrent.TimeUnit
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.
@ -90,6 +88,7 @@ class ReaderPresenter(
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(),
@ -393,7 +392,13 @@ class ReaderPresenter(
if (chapter.pageLoader is HttpPageLoader) {
val manga = manga ?: return
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) {
chapter.state = ReaderChapter.State.Wait
}
@ -406,7 +411,6 @@ class ReaderPresenter(
logcat { "Preloading ${chapter.chapter.url}" }
val loader = loader ?: return
loader.loadChapter(chapter)
.observeOn(AndroidSchedulers.mainThread())
// Update current chapters whenever a chapter is preloaded
@ -447,7 +451,7 @@ class ReaderPresenter(
loadNewChapter(selectedChapter)
}
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) {
downloadNextChapters()
}
@ -455,45 +459,31 @@ class ReaderPresenter(
private fun downloadNextChapters() {
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
val nextChapter = viewerChaptersRelay.value?.nextChapter?.chapter ?: return
val chaptersNumberToDownload = downloadPreferences.autoDownloadWhileReading().get()
if (chaptersNumberToDownload == 0 || !manga.favorite) return
val isNextChapterDownloadedOrQueued = downloadManager.isChapterDownloaded(
nextChapter.name,
nextChapter.scanlator,
manga.title,
manga.source,
skipCache = true,
) || downloadManager.getChapterDownloadOrNull(nextChapter) != null
if (isNextChapterDownloadedOrQueued) {
downloadAutoNextChapters(chaptersNumberToDownload, nextChapter.id, nextChapter.read)
presenterScope.launchIO {
val isNextChapterDownloaded = downloadManager.isChapterDownloaded(
nextChapter.name,
nextChapter.scanlator,
manga.title,
manga.source,
)
if (!isNextChapterDownloaded) return@launchIO
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
* 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),
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),
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),
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),
REVERSE_PORTRAIT(6, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, R.string.rotation_reverse_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000030),
;
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.history.interactor.DeleteAllHistory
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.RemoveHistoryByMangaId
import eu.kanade.domain.history.model.HistoryWithRelations
@ -37,7 +37,7 @@ import java.util.Date
class HistoryPresenter(
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
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 removeHistoryById: RemoveHistoryById = Injekt.get(),
private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
@ -94,7 +94,7 @@ class HistoryPresenter(
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
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)
}
}
@ -111,7 +111,7 @@ class HistoryPresenter(
fun resumeLastChapterRead() {
presenterScope.launchIO {
val chapter = getNextChapter.await()
val chapter = getNextUnreadChapters.await()
_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.system.logcat
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
@ -87,11 +88,11 @@ class UpdatesPresenter(
getUpdates.subscribe(calendar).distinctUntilChanged(),
downloadCache.changes,
) { updates, _ -> updates }
.onStart { delay(500) } // Defer to avoid crashing on initial render
.catch {
logcat(LogPriority.ERROR, it)
_events.send(Event.InternalError)
}
.stateIn(presenterScope)
.collectLatest { updates ->
state.items = updates.toUpdateItems()
state.isLoading = false

View File

@ -1,5 +1,3 @@
package eu.kanade.tachiyomi.util.system
fun Boolean.toInt() = if (this) 1 else 0
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 eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@ -393,8 +394,8 @@ fun Context.isPackageInstalled(packageName: String): Boolean {
}
}
fun Context.getInstallerPackageName(): String? {
return try {
fun Context.isInstalledFromFDroid(): Boolean {
val installerPackageName = try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
packageManager.getInstallSourceInfo(packageName).installingPackageName
} else {
@ -404,6 +405,10 @@ fun Context.getInstallerPackageName(): String? {
} catch (e: Exception) {
null
}
return installerPackageName == "org.fdroid.fdroid" ||
// F-Droid builds typically disable the updater
(!BuildConfig.INCLUDE_UPDATER && !isDevFlavor)
}
fun Context.getApplicationIcon(pkgName: String): Drawable? {

View File

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

View File

@ -32,10 +32,12 @@ SELECT *
FROM mangas
WHERE _id = :id;
-- TODO: this should ideally never really have more than 1 result
getMangaByUrlAndSource:
SELECT *
FROM mangas
WHERE url = :url AND source = :source;
WHERE url = :url AND source = :source
LIMIT 1;
getFavorites:
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-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", 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" }

View File

@ -1,18 +1,19 @@
[versions]
kotlin_version = "1.7.20"
coroutines_version = "1.6.4"
serialization_version = "1.4.1"
serialization_version = "1.4.0"
xml_serialization_version = "0.84.3"
[libraries]
reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", 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-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.6.4" }
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" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", 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 = { 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-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" }

View File

@ -8,7 +8,7 @@ flowbinding_version = "1.2.0"
shizuku_version = "12.2.0"
sqldelight = "1.5.4"
leakcanary = "2.9.1"
voyager = "1.0.0-rc05"
voyager = "1.0.0-rc06"
[libraries]
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="publishing_finished">সম্পূর্ণ প্রকাশিত</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="appwidget_updates_description">সম্প্রতি আপনার হালনাগাদকৃত মাঙ্গা দেখুন</string>
<string name="are_you_sure">আপনি কি নিশ্চিত\?</string>

View File

@ -2,7 +2,7 @@
<resources>
<string name="name">Nom</string>
<string name="categories">Categories</string>
<string name="manga">Manga</string>
<string name="manga">Mangues</string>
<string name="chapters">Capítols</string>
<string name="track">En seguiment</string>
<string name="history">Historial</string>
@ -69,7 +69,7 @@
<string name="pref_category_general">General</string>
<string name="pref_category_reader">Lector</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_about">Quant a</string>
<string name="pref_library_columns">Elements per fila</string>
@ -85,7 +85,7 @@
<string name="all">Tot</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="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="default_category">Categoria per defecte</string>
<string name="default_category_summary">Demana-ho sempre</string>
@ -94,7 +94,7 @@
<string name="ext_pending">Pendent</string>
<string name="ext_downloading">S\'està baixant</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_untrusted">No és de confiança</string>
<string name="ext_uninstall">Desinstal·la</string>
@ -179,8 +179,8 @@
<string name="pref_clear_cookies">Esborra 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_summary">Suprimeix l\'historial 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 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 dels elements que no siguin a la biblioteca</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_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="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_reset">Reinicia tots els capítols d\'aquest manga</string>
<string name="snack_add_to_library">Voleu afegir el manga a la biblioteca\?</string>
<string name="dialog_with_checkbox_reset">Reinicia tots els capítols d\'aquest element</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="custom_filter">Filtre personalitzat</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="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_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_backup">Seleccioneu el fitxer de còpia de seguretat</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_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_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_update_available">Nova versió disponible!</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="label_help">Ajuda</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_forward">Endavant</string>
<string name="action_webview_refresh">Actualitza</string>
<string name="pref_category_library">Biblioteca</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_category_library_update">Actualització global</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="pref_always_show_chapter_transition">Mostra sempre la transició de capítol</string>
<plurals name="notification_new_chapters_summary">
<item quantity="one">Per a %d títol</item>
<item quantity="other">Per a %d títols</item>
<item quantity="one">Per a %d element</item>
<item quantity="other">Per a %d elements</item>
</plurals>
<string name="action_menu">Menú</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="label_sources">Fonts</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="add_tracking">Segueix</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="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="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="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">
<item quantity="one">En resta %1$s</item>
<item quantity="other">En resten %1$s</item>
@ -433,14 +433,14 @@
<item quantity="other">%d categories</item>
</plurals>
<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="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="sort_by_upload_date">Per data de pujada</string>
<string name="label_data">Dades</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="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>
@ -499,13 +499,13 @@
<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="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="chapter_settings">Configuració dels capítols</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="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="pref_incognito_mode_summary">Pausa l\'historial de lectura</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="notification_incognito_text">Desactiva el mode d\'incògnit</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_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_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">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="rotation_landscape">Horitzontal</string>
<string name="rotation_portrait">Vertical</string>
@ -559,7 +559,7 @@
<string name="nav_zone_next">Següent</string>
<string name="nav_zone_prev">Anterior</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_reader_actions">Accions</string>
<string name="pref_grayscale">Escala de grisos</string>
@ -570,7 +570,7 @@
<string name="none">Cap</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="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="local_filter_order_by">Ordena per</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="categorized_display_settings">Opcions de visualització i ordenació per categoria</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_low">Baixa</string>
<string name="pref_high">Alta</string>
@ -606,7 +606,7 @@
<string name="theme_greenapple">Verd poma</string>
<string name="pref_category_appearance">Aparença</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="theme_monet">Dinàmic</string>
<string name="recently">Recentment</string>
@ -640,14 +640,14 @@
<string name="ext_installer_pref">Instal·lador</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="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="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="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="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_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>
@ -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="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="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).
\n
\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_empty_screen">No s\'ha trobat cap font</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_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>
@ -728,7 +728,7 @@
<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="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="update_already_running">Ja s\'està executant una actualització</string>
<string name="theme_tidalwave">Tsunami</string>
@ -746,7 +746,7 @@
<item quantity="other">Els següents %d capítols no llegits</item>
</plurals>
<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_reader_summary">Mode de lectura, visualització i navegació</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="pref_long_strip_split">Divideix les imatges altes (BETA)</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>

View File

@ -145,7 +145,7 @@
<string name="update_check_confirm">Stáhnout</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_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_update_available">Dostupná 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="pref_category_library">Knihovna</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="pref_date_format">Formát data</string>
<string name="pref_category_library_update">Globální aktualizace</string>
@ -488,9 +488,9 @@
<item quantity="other">%1$d nových kapitol</item>
</plurals>
<plurals name="notification_new_chapters_summary">
<item quantity="one">Pro %d titul</item>
<item quantity="few">Pro %d tituly</item>
<item quantity="other">Pro %d titulů</item>
<item quantity="one">Pro %d položku</item>
<item quantity="few">Pro %d položky</item>
<item quantity="other">Pro %d položek</item>
</plurals>
<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>
@ -567,7 +567,7 @@
<string name="rotation_landscape">Na šířku</string>
<string name="rotation_portrait">Na výšku</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_reader_actions">Akce</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="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="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="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="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="database_clean">Nic k vyčištění</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="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="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>

View File

@ -559,4 +559,31 @@
<string name="pref_show_navigation_mode_summary">Пусма вырӑнсене вулӑш уҫӑ чухне кӑтартмалла</string>
<string name="action_show_errors">Йӑнӑшсене кӑтарт</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>

View File

@ -221,7 +221,7 @@
<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_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_update_available">Neue Version verfügbar!</string>
<string name="information_no_downloads">Keine Downloads</string>
@ -308,7 +308,7 @@
<string name="action_webview_refresh">Aktualisieren</string>
<string name="pref_category_library">Bibliothek</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_category_library_update">Globale Aktualisierung</string>
<string name="logout_title">Aus %1$s abmelden\?</string>
@ -361,8 +361,8 @@
<string name="email">E-Mail-Adresse</string>
<string name="pref_always_show_chapter_transition">Kapitelübergang immer anzeigen</string>
<plurals name="notification_new_chapters_summary">
<item quantity="one">Für %d Titel</item>
<item quantity="other">Für %d Titel</item>
<item quantity="one">Für %d Eintrag</item>
<item quantity="other">Für %d Einträge</item>
</plurals>
<string name="action_menu">Menü</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="rotation_landscape">Querformat</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="rotation_type">Ausrichtungstyp</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="privacy_policy">Datenschutzbestimmungen</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="save_chapter_as_cbz">Als CBZ-Archiv speichern</string>
<string name="cancelled">Abgebrochen</string>
@ -766,4 +766,10 @@
<string name="invalid_location">Ungültiger Speicherort: %s</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="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>

View File

@ -5,7 +5,7 @@
<string name="manga">Manga</string>
<string name="chapters">Κεφάλαια</string>
<string name="track">Tracking</string>
<string name="history">Ιστορία</string>
<string name="history">Ιστορικό</string>
<string name="label_settings">Ρυθμίσεις</string>
<string name="label_download_queue">Ουρά λήψεων</string>
<string name="label_library">Βιβλιοθήκη</string>
@ -169,7 +169,7 @@
<string name="pref_backup_slots">Μέγιστα αντίγραφα ασφαλείας</string>
<string name="backup_created">Δημιουργήθηκε αντίγραφο ασφαλείας</string>
<string name="restore_completed">Η επαναφορά ολοκληρώθηκε</string>
<string name="backup_choice">Τι θέλετε να κάνετε backup;</string>
<string name="backup_choice">Τι αντίγραφο ασφαλείας θέλετε να δημιουργήσετε;</string>
<string name="restoring_backup">Επαναφορά αντιγράφων ασφαλείας</string>
<string name="creating_backup">Δημιουργία αντιγράφων ασφαλείας</string>
<string name="pref_clear_chapter_cache">Καθάρισμα προσωρινής μνήμης κεφαλαίου</string>
@ -179,8 +179,8 @@
<string name="pref_clear_cookies">Διαγραφή cookies</string>
<string name="cookies_cleared">Τα cookies διαγράφηκαν</string>
<string name="pref_clear_database">Καθαρισμός βάσης δεδομένων</string>
<string name="pref_clear_database_summary">Διαγραφή ιστορικού για manga που δεν είναι αποθηκευμένα στη βιβλιοθήκη σας</string>
<string name="clear_database_confirmation">Είστε σίγουροι; Τα διαβασμένα κεφάλαια και η πρόοδος των manga εκτός βιβλιοθήκης θα χαθεί</string>
<string name="pref_clear_database_summary">Διαγραφή ιστορικού για καταχωρήσεις που δεν έχουν αποθηκευτεί στη βιβλιοθήκη σας</string>
<string name="clear_database_confirmation">Είστε σίγουροι; Τα διαβασμένα κεφάλαια και η πρόοδος των καταχωρήσεων εκτός βιβλιοθήκης θα χαθούν</string>
<string name="clear_database_completed">Οι καταχωρίσεις διαγράφηκαν</string>
<string name="pref_refresh_library_tracking">Ανανέωση tracking</string>
<string name="pref_refresh_library_tracking_summary">Ενημερώνει κατάσταση, βαθμολογία και τελευταίο αναγνωσμένο κεφάλαιο από τις υπηρεσίες παρακολούθησης</string>
@ -244,8 +244,8 @@
<string name="error_category_exists">Μια κατηγορία με αυτό το όνομα υπάρχει ήδη!</string>
<string name="snack_categories_deleted">Οι κατηγορίες διαγράφηκαν</string>
<string name="dialog_with_checkbox_remove_description">Αυτό θα αφαιρέσει την ημερομηνία ανάγνωσης αυτού του κεφαλαίου. Είστε σίγουροι;</string>
<string name="dialog_with_checkbox_reset">Επαναφορά όλων των κεφαλαίων για αυτό το manga</string>
<string name="snack_add_to_library">Προσθήκη manga στη βιβλιοθήκη;</string>
<string name="dialog_with_checkbox_reset">Επαναφορά όλων των κεφαλαίων για αυτήν την καταχώρηση</string>
<string name="snack_add_to_library">Προσθήκη στη βιβλιοθήκη;</string>
<string name="picture_saved">Η εικόνα αποθηκεύτηκε</string>
<string name="custom_filter">Προσαρμοσμένο φίλτρο</string>
<string name="set_as_cover">Ορισμός ως εξώφυλλο</string>
@ -268,14 +268,14 @@
<string name="download_queue_error">Δεν ήταν δυνατή η λήψη κεφαλαίων. Μπορείτε να δοκιμάσετε ξανά στο τμήμα λήψεων</string>
<string name="notification_new_chapters">Βρέθηκαν νέα κεφάλαια</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_backup">Επιλέξτε αρχείο αντιγράφου ασφαλείας</string>
<string name="update_check_confirm">Λήψη</string>
<string name="update_check_no_new_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_complete">Πατήστε για εγκατάσταση</string>
<string name="update_check_notification_download_complete">Πατήστε για να εγκαταστήσετε την ενημέρωση</string>
<string name="update_check_notification_download_error">Σφάλμα λήψης</string>
<string name="update_check_notification_update_available">Υπάρχει διαθέσιμη ενημέρωση!</string>
<string name="information_no_downloads">Δεν υπάρχουν λήψεις</string>
@ -308,7 +308,7 @@
<string name="action_webview_refresh">Ανανέωση</string>
<string name="pref_category_library">Βιβλιοθήκη</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="last_used_source">Χρησιμοποιήθηκε τελευταία</string>
<string name="add_tracking">Προσθήκη tracking</string>
@ -413,7 +413,7 @@
<item quantity="one">Κεφάλαια %1$s και 1 ακόμη</item>
<item quantity="other">Κεφάλαια %1$s και %2$d ακόμη</item>
</plurals>
<string name="downloaded_only_summary">Φιλτράρει όλα τα manga στη βιβλιοθήκη σας</string>
<string name="downloaded_only_summary">Φιλτράρει όλες τις καταχωρήσεις στη βιβλιοθήκη σας</string>
<string name="pref_search_pinned_sources_only">Να περιέχονται μόνο καρφιτσωμένες πηγές</string>
<plurals name="download_queue_summary">
<item quantity="one">%1$s που απομένει</item>
@ -433,14 +433,14 @@
<item quantity="other">Έγινε σε %1$s με %2$s σφάλματα</item>
</plurals>
<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="unofficial_extension_message">Αυτή η επέκταση δεν προέρχεται από την επίσημη λίστα επεκτάσεων Tachiyomi.</string>
<string name="ext_unofficial">Ανεπίσημη</string>
<string name="sort_by_upload_date">Από ημερομηνία μεταφόρτωσης</string>
<string name="label_data">Δεδομένα</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="pref_library_update_refresh_metadata_summary">Έλεγχος για νέο εξώφυλλο και λεπτομέρειες κατά την ενημέρωση της βιβλιοθήκης</string>
<string name="pref_library_update_refresh_metadata">Αυτόματη ανανέωση μεταδεδομένων</string>
@ -499,17 +499,17 @@
<string name="no_chapters_error">Δεν βρέθηκαν κεφάλαια</string>
<string name="chapter_settings_updated">Ενημερώθηκαν οι προεπιλεγμένες ρυθμίσεις κεφαλαίου</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="chapter_settings">Ρυθμίσεις κεφαλαίου</string>
<string name="share_page_info">%1$s: %2$s, σελίδα %3$d</string>
<string name="action_search_settings">Αναζήτηση ρυθμίσεων</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">Λειτουργία ανώνυμης περιήγησης</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="spen_next_page">Επόμενη σελίδα</string>
<string name="spen_previous_page">Προηγούμενη σελίδα</string>
@ -552,15 +552,15 @@
<string name="include">Συμπερίληψη: %s</string>
<string name="none">Κανένα</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_library_update_categories_details">Τα manga στις αποκλεισμένες κατηγορίες δεν θα ενημερώνονται ακόμη και αν βρίσκονται επίσης στις συμπεριλαμβανόμενες κατηγορίες.</string>
<string name="pref_library_update_categories_details">Οι καταχωρίσεις σε εξαιρούμενες κατηγορίες δε θα ενημερώνονται ακόμη και αν περιλαμβάνονται επίσης σε κατηγορίες που περιλαμβάνονται.</string>
<string name="action_show_errors">Πατήστε για να δείτε λεπτομέρειες</string>
<string name="update_check_eol">Αυτή η έκδοση Android δεν υποστηρίζεται πλέον</string>
<string name="clipboard_copy_error">Απέτυχε η αντιγραφή στο πρόχειρο</string>
<string name="rotation_landscape">Οριζόντια</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_reader_actions">Ενέργειες</string>
<string name="rotation_type">Τύπος περιστροφής</string>
@ -578,7 +578,7 @@
<string name="local_invalid_format">Μη έγκυρη μορφή κεφαλαίου</string>
<string name="chapter_not_found">Το κεφάλαιο δε βρέθηκε</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="on">Ενεργοποιημένο</string>
<string name="error_sharing_cover">Σφάλμα κοινής χρήσης του εξωφύλλου</string>
@ -600,7 +600,7 @@
<string name="theme_yinyang">Γιν και Γιανγκ</string>
<string name="theme_tako">Tako</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="enhanced_services">Βελτιωμένες υπηρεσίες</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_stopped">Το Shizuku δεν τρέχει</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_summary">Εκτύπωση λεπτομερών αρχείων καταγραφής στο αρχείο καταγραφής συστήματος (μειώνει την απόδοση της εφαρμογής)</string>
<string name="action_display_language_badge">Γλώσσα</string>
@ -650,7 +650,7 @@
<string name="ext_update_all">Ενημέρωση όλων</string>
<string name="channel_app_updates">Ενημερώσεις εφαρμογής</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="extension_api_error">Απέτυχε η λήψη λίστας επεκτάσεων</string>
<string name="privacy_policy">Πολιτική απορρήτου</string>
@ -663,7 +663,7 @@
<string name="publishing_finished">Η έκδοσή ολοκληρώθηκε</string>
<string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</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="confirm_manga_add_duplicate">Έχετε μια καταχώρηση στη βιβλιοθήκη σας με το ίδιο όνομα αλλά από διαφορετική πηγή (%1$s).
\n
@ -694,7 +694,7 @@
<string name="battery_not_low">Όταν η μπαταρία δεν είναι χαμηλή</string>
<string name="source_filter_empty_screen">Δε βρέθηκε εγκατεστημένη πηγή</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="split_tall_images">Διαχωρισμός ψηλών εικόνων</string>
<string name="split_tall_images_summary">Βελτιώνει την απόδοση του αναγνώστη</string>
@ -728,7 +728,7 @@
<string name="pref_reset_user_agent_string">Επαναφορά προεπιλεγμένης συμβολοσειράς πράκτορα χρήστη</string>
<string name="action_remove_everything">Καταργήστε τα πάντα</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="update_already_running">Εκτελείται ήδη μια ενημέρωση</string>
<string name="error_user_agent_string_blank">Η συμβολοσειρά πράκτορα χρήστη δεν μπορεί να είναι κενή</string>
@ -766,4 +766,10 @@
<string name="invalid_location">Μη έγκυρη τοποθεσία: %s</string>
<string name="error_user_agent_string_invalid">Μη έγκυρη συμβολοσειρά πράκτορα χρήστη</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>

View File

@ -184,7 +184,7 @@
<string name="update_check_look_for_updates">Buscando actualizaciones…</string>
<!--UpdateCheck Notifications-->
<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_update_available">¡Nueva versión disponible!</string>
<!--Content Description-->
@ -340,7 +340,7 @@
<string name="action_webview_refresh">Actualizar</string>
<string name="pref_category_library">Biblioteca</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_category_library_update">Actualización global</string>
<string name="logout">Cerrar sesión</string>
@ -810,4 +810,9 @@
<string name="invalid_location">Ubicación incorrecta: %s</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="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>

View File

@ -672,4 +672,12 @@
\n
\nJarraitu nahi duzu\?</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>

View File

@ -94,8 +94,8 @@
<string name="label_recent_manga">Nakaraan</string>
<string name="label_recent_updates">Bago</string>
<string name="label_library">Aklatan</string>
<string name="label_download_queue">Dina-download</string>
<string name="label_settings">Pagsasaayos</string>
<string name="label_download_queue">Mga Dina-download</string>
<string name="label_settings">Mga Settings</string>
<string name="label_more">Higit pa</string>
<string name="name">Pangalan</string>
<plurals name="lock_after_mins">
@ -105,7 +105,7 @@
<string name="lock_never">Hindi</string>
<string name="lock_always">Palagi</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_category_security">Seguridad</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_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="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.
\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.
@ -246,7 +246,7 @@
<string name="secure_screen">Bantayan ang screen</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="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="backup_created">Nai-backup na</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_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="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="pref_auto_update_manga_sync">I-update ang progress pagkabasa</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_cloudflare_bypass_failure">Bigong ma-bypass ang Cloudflare</string>
<plurals name="update_check_notification_ext_updates">
<item quantity="one">Merong update sa extension</item>
<item quantity="other">May %d (na) update sa extension</item>
<item quantity="one">Ang extension ay available upang i-update</item>
<item quantity="other">Ang mga %d (na) extension ay available upang i-update</item>
</plurals>
<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_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_look_for_updates">Naghahanap ng mga update…</string>
<string name="update_check_no_new_updates">Walang bagong update</string>
<string name="update_check_confirm">I-download</string>
<string name="file_select_backup">Pumili ng backup</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>
<plurals name="notification_chapters_multiple_and_more">
<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="custom_filter">Pinili kong filter</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="snack_categories_deleted">Binura na ang mga kategorya</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_downloading_progress">Dina-download (%1$d/%2$d)</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="copied_to_clipboard">Kinopya sa clipboard:
\n%1$s</string>
@ -431,9 +431,9 @@
<string name="login_title">Mag-login sa %1$s</string>
<plurals name="download_queue_summary">
<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>
<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="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>
@ -451,8 +451,8 @@
<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="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="pref_clear_database_summary">Burahin ang nakaraan ng mga manga na hindi nakalagay sa Aklatan mo</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 history para sa mga entry na hindi naka-save sa iyong library</string>
<string name="pref_clear_database">Linisin ang database</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>
@ -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_short">18+</string>
<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="other">Lalaktawan ang %d (na) kabanata, siguro baka sa source ang mga ito, o baka na-filter sila</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 mga chapter, maaaring wala ang pinagmulan nito o na-filter na ito</item>
</plurals>
<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="set_chapter_settings_as_default">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="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="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="pref_incognito_mode_summary">Hinihinto ang pagtala sa nakaraan</string>
<string name="pref_incognito_mode">Nakatago</string>
@ -542,7 +542,7 @@
\n
\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_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="nav_zone_right">Kanan</string>
<string name="nav_zone_left">Kaliwa</string>
@ -553,7 +553,7 @@
<string name="exclude">Di-kasama: %s</string>
<string name="include">Kasama: %s</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_show_errors">Pindutin para makita ang detalye</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="off">Patayin</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="information_empty_category_dialog">Wala ka pang kategorya.</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="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="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="theme_midnightdusk">Hatinggabi</string>
<string name="theme_greenapple">Berdeng Mansanas</string>
@ -616,7 +616,7 @@
<string name="pref_relative_format">Pagpepetsa</string>
<plurals name="relative_time">
<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>
<string name="recently">Kamakailan</string>
<string name="relative_time_today">Ngayon</string>
@ -636,7 +636,7 @@
<string name="ext_installer_legacy">Legasiya</string>
<string name="ext_installer_pref">Taga-install</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_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>
@ -649,7 +649,7 @@
<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="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="database_clean">Walang malilinis</string>
<string name="extension_api_error">Bigong makuha ang mga extension</string>
@ -663,7 +663,7 @@
<string name="cancelled">Kinansela</string>
<string name="action_faq_and_guides">Mga Madalas Itanong at Gabay</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_display_cover_only_grid">Pabalat lang</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="source_filter_empty_screen">Walang nakitang naka-install na 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="split_tall_images">Hatiin ang mga matatangkad na larawan</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="action_remove_everything">Burahin lahat</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="update_already_running">May ina-update sa ngayon</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="are_you_sure">Sigurado ka ba\?</string>
<plurals name="next_unread_chapters">
<item quantity="one">Susunod na kabanatang di pa nabasa</item>
<item quantity="other">Susunod na %d (na) kabanatang di pa nabasa</item>
<item quantity="one">Susunod ang hindi pa nababasa na chapter</item>
<item quantity="other">Susunod ang mga %d na hindi pa nababasa na mga chapter</item>
</plurals>
<string name="multi_lang">Marami</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="popular">Sikat</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="crash_screen_restart_application">Buksan muli ang app</string>
<string name="invalid_location">Hindi wastong lokasyon: %s</string>
<string name="unknown_title">Di tiyak na pamagat</string>
<string name="error_user_agent_string_invalid">Hindi wastong user agent</string>
<string name="unknown_title">Hindi alam ang Pamagat</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="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>

View File

@ -376,7 +376,7 @@
<string name="pref_library_columns">פריטים לשורה</string>
<string name="theme_system">עקוב אחר המערכת</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_select_inverse">בחר את ההפך</string>
<string name="action_display_language_badge">שפה</string>
@ -408,7 +408,7 @@
<string name="action_migrate">העברה</string>
<string name="action_display_comfortable_grid">רשת נוחה</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="pref_dark_theme_pure_black">מצב שחור כהה טהור</string>
<string name="theme_greenapple">ירוק תפוח</string>
@ -425,7 +425,7 @@
<string name="action_sort_chapter_fetch_date">תאריך האחזור של הפרקים</string>
<string name="theme_monet">דינמי</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="pref_update_only_completely_read">עם פרק(ים) שלא נקרא(ו)</string>
<string name="action_move_to_top_all_for_series">העבר סדרה לראש</string>
@ -626,7 +626,7 @@
<string name="learn_more">לחץ כדי ללמוד עוד</string>
<string name="pref_clear_history">נקה היסטוריה</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="battery_not_low">אין קצת סוללה</string>
<string name="categorized_display_settings">הגדרות מיון ותצוגה לכל קטגוריה בנפרד</string>
@ -685,4 +685,8 @@
<string name="pref_dual_page_invert_summary">אם המקום של הפיצול עמוד לא תואם לכיוון הקריאה</string>
<string name="tapping_inverted_none">כלום</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>

View File

@ -523,7 +523,7 @@
<string name="action_display_show_number_of_items">Tampilkan jumlah item</string>
<string name="none">Kosong</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="nav_zone_right">Kanan</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="source_filter_empty_screen">Sumber yang diinstal tidak 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="split_tall_images">Membagi gambar panjang</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>
<!--UpdateCheck Notifications-->
<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_update_available">Nuova versione disponibile!</string>
<!--Content Description-->
@ -341,7 +341,7 @@
<string name="action_webview_refresh">Aggiorna</string>
<string name="pref_category_library">Libreria</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_category_library_update">Aggiornamenti globali</string>
<string name="logout_title">Disconnettersi da %1$s\?</string>
@ -812,4 +812,10 @@
<string name="unknown_title">Titolo sconosciuto</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="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>

View File

@ -264,7 +264,7 @@
<string name="update_check_no_new_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_complete">タップでインストール</string>
<string name="update_check_notification_download_complete">タップでアップデートをインストール</string>
<string name="update_check_notification_download_error">ダウンロード中にエラー発生</string>
<string name="update_check_notification_update_available">アップデート利用可能!</string>
<string name="information_no_downloads">ダウンロードがありません</string>
@ -302,7 +302,7 @@
<string name="action_webview_refresh">再ロード</string>
<string name="pref_category_library">ライブラリ</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_category_library_update">グローバルアップデート</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_multiple">第%1$s章</string>
<plurals name="notification_new_chapters_summary">
<item quantity="other">%d件のタイトル</item>
<item quantity="other">%d件の項目</item>
</plurals>
<string name="recent_manga_time">第%1$s章 - %2$s</string>
<string name="page_list_empty_error">ページが見つかりません</string>
@ -642,7 +642,7 @@
<string name="database_clean">消去できるものはありませんでした</string>
<string name="extension_api_error">拡張機能リストを取得できませんでした</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="library_errors_help">ライブラリ更新エラーの修正については、%1$sをご覧ください</string>
<string name="save_chapter_as_cbz">CBZアーカイブとして保存</string>
@ -753,4 +753,10 @@
<string name="pref_advanced_summary">クラッシュ ログのダンプ、バッテリーの最適化</string>
<string name="pref_appearance_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>

View File

@ -225,7 +225,7 @@
<string name="update_check_no_new_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_complete">탭하여 설치</string>
<string name="update_check_notification_download_complete">탭하여 업데이트 설치</string>
<string name="update_check_notification_download_error">다운로드 오류</string>
<string name="update_check_notification_update_available">업데이트 이용 가능!</string>
<string name="information_no_downloads">다운로드 없음</string>
@ -272,7 +272,7 @@
<string name="custom_download">다운로드 할 회차 직접 입력</string>
<string name="download_custom">사용자 정의</string>
<string name="reading">읽는 중</string>
<string name="completed"></string>
<string name="completed"></string>
<string name="dropped">포기함</string>
<string name="on_hold">일시중지중</string>
<string name="plan_to_read">계획중</string>
@ -546,7 +546,7 @@
<string name="information_webview_required">Tachiyomi를 사용하려면 WebView가 필요합니다</string>
<string name="information_webview_outdated">호환성을 위해 WebView 어플리케이션을 업데이트 해 주세요</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_installer_legacy">레거시</string>
<string name="webtoon_side_padding_10">10%</string>
@ -753,4 +753,10 @@
<string name="invalid_location">잘못된 위치: %s</string>
<string name="error_user_agent_string_invalid">잘못된 사용자 에이전트 문자열</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>

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