mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-26 11:07:51 +02:00
Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
3222247969 | |||
dd6c9ce2fe | |||
7446b28ff1 | |||
38c6702b8f | |||
afcf4b2988 | |||
ebb96a6ff4 | |||
8b0affe9bd | |||
1a25cea0d6 | |||
2ecbcdf4bd | |||
642b392d44 | |||
8dce7b3e9e | |||
33e90d6449 | |||
50b17d5d34 | |||
7818885406 | |||
26af7ccc77 | |||
5d1f79012e | |||
cac80daa71 | |||
fc184f1cfa | |||
725fcbba0e | |||
bdeb209d43 | |||
a078f1ab1b | |||
86c3d8c064 | |||
156191af44 | |||
57bba9e5ab | |||
dd1923fe88 | |||
df773ee15c | |||
f5451a6881 | |||
fcec1581b7 | |||
11cc789e36 | |||
16f9fb2f40 | |||
6bfaa85e84 | |||
8f43fb9530 | |||
04d2a3399b | |||
054bf8ec5d | |||
8417f5a63c | |||
26b46cace0 | |||
0849111247 | |||
f9c25b350e |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -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
|
||||
|
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -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
|
||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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()) }
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 -> {
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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) },
|
||||
)
|
||||
}
|
||||
|
@ -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 ->
|
||||
|
@ -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,
|
||||
)
|
||||
},
|
||||
|
@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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))
|
||||
},
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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() },
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
@ -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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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(
|
||||
|
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal file
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal 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]) },
|
||||
)
|
||||
}
|
||||
}
|
@ -42,7 +42,6 @@ fun Pill(
|
||||
text = text,
|
||||
fontSize = fontSize,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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))
|
||||
}
|
||||
},
|
||||
|
@ -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) },
|
||||
|
@ -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) } },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 },
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
)
|
||||
|
@ -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),
|
||||
|
@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
},
|
||||
|
@ -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))
|
||||
}
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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 } },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -12,3 +12,4 @@ val verticalPadding = vertical
|
||||
val topPaddingValues = PaddingValues(top = vertical)
|
||||
|
||||
const val ReadItemAlpha = .38f
|
||||
const val SecondaryItemAlpha = .78f
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()) },
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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? {
|
||||
|
@ -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">
|
||||
|
@ -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 *
|
||||
|
@ -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" }
|
||||
|
@ -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" }
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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">N’esteu segur\? Es perdrà tot l’historial.</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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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
Reference in New Issue
Block a user