fix:conflict.

Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
KaiserBh 2023-08-03 01:13:39 +10:00
commit 70452acdda
No known key found for this signature in database
GPG Key ID: 14D73B142042BBA9
328 changed files with 37229 additions and 22851 deletions

View File

@ -66,11 +66,11 @@ android {
initWith(getByName("release"))
buildConfigField("boolean", "PREVIEW", "true")
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks.add("release")
val debugType = getByName("debug")
signingConfig = debugType.signingConfig
versionNameSuffix = debugType.versionNameSuffix
applicationIdSuffix = debugType.applicationIdSuffix
matchingFallbacks.add("release")
}
create("benchmark") {
initWith(getByName("release"))

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
}
override fun component2(): (T) -> Unit {
return { preference.set(it) }
return preference::set
}
}

View File

@ -56,6 +56,7 @@ import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.release.interactor.GetApplicationRelease
@ -100,10 +101,11 @@ class DomainModule : InjektModule {
addFactory { GetNextChapters(get(), get(), get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { SetFetchInterval(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get()) }
addFactory { UpdateManga(get(), get()) }
addFactory { SetMangaCategories(get()) }
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }

View File

@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
override fun key() = "extension_installer"
val entries get() = ExtensionInstaller.values().run {
val entries get() = ExtensionInstaller.entries.run {
if (context.hasMiuiPackageInstaller) {
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
} else {

View File

@ -72,9 +72,9 @@ class SetReadStatus(
suspend fun await(manga: Manga, read: Boolean) =
await(manga.id, read)
sealed class Result {
object Success : Result()
object NoChapters : Result()
data class InternalError(val error: Throwable) : Result()
sealed interface Result {
data object Success : Result
data object NoChapters : Result
data class InternalError(val error: Throwable) : Result
}
}

View File

@ -23,6 +23,7 @@ import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Long.max
import java.time.ZonedDateTime
import java.util.Date
import java.util.TreeSet
@ -48,11 +49,15 @@ class SyncChaptersWithSource(
rawSourceChapters: List<SChapter>,
manga: Manga,
source: Source,
manualFetch: Boolean = false,
fetchWindow: Pair<Long, Long> = Pair(0, 0),
): List<Chapter> {
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
throw NoChaptersException()
}
val now = ZonedDateTime.now()
val sourceChapters = rawSourceChapters
.distinctBy { it.url }
.mapIndexed { i, sChapter ->
@ -134,14 +139,21 @@ class SyncChaptersWithSource(
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
updateManga.awaitUpdateFetchInterval(
manga,
now,
fetchWindow,
)
}
return emptyList()
}
val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
val deletedChapterNumbers = TreeSet<Double>()
val deletedReadChapterNumbers = TreeSet<Double>()
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
toDelete.forEach { chapter ->
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
@ -188,6 +200,7 @@ class SyncChaptersWithSource(
val chapterUpdates = toChange.map { it.toChapterUpdate() }
updateChapter.awaitAll(chapterUpdates)
}
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
// Set this manga as updated since chapters were changed
// Note that last_update actually represents last time the chapter list changed at all

View File

@ -12,7 +12,7 @@ fun Chapter.toSChapter(): SChapter {
it.url = url
it.name = name
it.date_upload = dateUpload
it.chapter_number = chapterNumber
it.chapter_number = chapterNumber.toFloat()
it.scanlator = scanlator
}
}
@ -22,7 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
name = sChapter.name,
url = sChapter.url,
dateUpload = sChapter.date_upload,
chapterNumber = sChapter.chapter_number,
chapterNumber = sChapter.chapter_number.toDouble(),
scanlator = sChapter.scanlator?.ifBlank { null },
)
}
@ -49,7 +49,7 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
it.last_page_read = lastPageRead.toInt()
it.date_fetch = dateFetch
it.date_upload = dateUpload
it.chapter_number = chapterNumber
it.chapter_number = chapterNumber.toFloat()
it.source_order = sourceOrder.toInt()
it.last_modified = lastModifiedAt
}

View File

@ -3,9 +3,7 @@ package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.interactor.getCurrentFetchRange
import tachiyomi.domain.manga.interactor.updateIntervalMeta
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
@ -17,6 +15,7 @@ import java.util.Date
class UpdateManga(
private val mangaRepository: MangaRepository,
private val setFetchInterval: SetFetchInterval,
) {
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
@ -77,19 +76,14 @@ class UpdateManga(
)
}
suspend fun awaitUpdateIntervalMeta(
suspend fun awaitUpdateFetchInterval(
manga: Manga,
chapters: List<Chapter>,
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
dateTime: ZonedDateTime = ZonedDateTime.now(),
window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
): Boolean {
val newMeta = updateIntervalMeta(manga, chapters, zonedDateTime, setCurrentFetchRange)
return if (newMeta != null) {
mangaRepository.update(newMeta)
} else {
true
}
return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
?.let { mangaRepository.update(it) }
?: false
}
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {

View File

@ -99,7 +99,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories:
title = ComicInfo.Title(chapter.name),
series = ComicInfo.Series(manga.title),
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
if ((it.rem(1) == 0.0F)) {
if ((it.rem(1) == 0.0)) {
ComicInfo.Number(it.toInt().toString())
} else {
ComicInfo.Number(it.toString())

View File

@ -22,7 +22,7 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
it.last_chapter_read = lastChapterRead.toFloat()
it.total_chapters = totalChapters.toInt()
it.status = status.toInt()
it.score = score
it.score = score.toFloat()
it.tracking_url = remoteUrl
it.started_reading_date = startDate
it.finished_reading_date = finishDate
@ -40,7 +40,7 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
lastChapterRead = last_chapter_read.toDouble(),
totalChapters = total_chapters.toLong(),
status = status.toLong(),
score = score,
score = score.toDouble(),
remoteUrl = tracking_url,
startDate = started_reading_date,
finishDate = finished_reading_date,

View File

@ -23,6 +23,7 @@ import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -30,6 +31,7 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -53,11 +55,9 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsState
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.screens.EmptyScreen
@ -65,7 +65,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
@Composable
fun ExtensionDetailsScreen(
navigateUp: () -> Unit,
state: ExtensionDetailsState,
state: ExtensionDetailsScreenModel.State,
onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickWhatsNew: () -> Unit,
onClickReadme: () -> Unit,
@ -314,7 +314,7 @@ private fun DetailsHeader(
}
}
Divider()
HorizontalDivider()
}
}
@ -356,11 +356,8 @@ private fun InfoText(
@Composable
private fun InfoDivider() {
Divider(
modifier = Modifier
.height(20.dp)
.width(1.dp),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
VerticalDivider(
modifier = Modifier.height(20.dp),
)
}

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen
@ -53,7 +53,7 @@ private fun ExtensionFilterContent(
onClickLang: (String) -> Unit,
) {
val context = LocalContext.current
FastScrollLazyColumn(
LazyColumn(
contentPadding = contentPadding,
) {
items(state.languages) { language ->

View File

@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsState
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.PullRefresh
@ -57,7 +57,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable
fun ExtensionScreen(
state: ExtensionsState,
state: ExtensionsScreenModel.State,
contentPadding: PaddingValues,
searchQuery: String?,
onLongClickItem: (Extension) -> Unit,
@ -108,7 +108,7 @@ fun ExtensionScreen(
@Composable
private fun ExtensionContent(
state: ExtensionsState,
state: ExtensionsScreenModel.State,
contentPadding: PaddingValues,
onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit,

View File

@ -1,49 +1,25 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.VerticalDivider
import tachiyomi.presentation.core.components.material.padding
@Composable
fun GlobalSearchScreen(
state: GlobalSearchScreenModel.State,
items: Map<CatalogueSource, SearchItemResult>,
state: SearchScreenModel.State,
navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
@ -56,80 +32,23 @@ fun GlobalSearchScreen(
) {
Scaffold(
topBar = { scrollBehavior ->
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
GlobalSearchToolbar(
searchQuery = state.searchQuery,
progress = state.progress,
total = state.total,
navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
scrollBehavior = scrollBehavior,
)
Row(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.padding(horizontal = MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
// TODO: make this UX better; it only applies when triggering a new search
FilterChip(
selected = state.sourceFilter == SourceFilter.PinnedOnly,
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.PushPin,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.pinned_sources))
},
)
FilterChip(
selected = state.sourceFilter == SourceFilter.All,
onClick = { onChangeSearchFilter(SourceFilter.All) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.DoneAll,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.all))
},
)
VerticalDivider()
FilterChip(
selected = state.onlyShowHasResults,
onClick = { onToggleResults() },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.has_results))
},
)
}
Divider()
}
GlobalSearchToolbar(
searchQuery = state.searchQuery,
progress = state.progress,
total = state.total,
navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
sourceFilter = state.sourceFilter,
onChangeSearchFilter = onChangeSearchFilter,
onlyShowHasResults = state.onlyShowHasResults,
onToggleResults = onToggleResults,
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
GlobalSearchContent(
items = items,
items = state.filteredItems,
contentPadding = paddingValues,
getManga = getManga,
onClickSource = onClickSource,
@ -140,7 +59,8 @@ fun GlobalSearchScreen(
}
@Composable
private fun GlobalSearchContent(
internal fun GlobalSearchContent(
fromSourceId: Long? = null,
items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues,
getManga: @Composable (Manga) -> State<Manga>,
@ -154,7 +74,7 @@ private fun GlobalSearchContent(
items.forEach { (source, result) ->
item(key = source.id) {
GlobalSearchResultItem(
title = source.name,
title = fromSourceId?.let { "${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
subtitle = LocaleHelper.getDisplayName(source.lang),
onClick = { onClickSource(source) },
) {
@ -163,18 +83,6 @@ private fun GlobalSearchContent(
GlobalSearchLoadingResultItem()
}
is SearchItemResult.Success -> {
if (result.isEmpty) {
Text(
text = stringResource(R.string.no_results_found),
modifier = Modifier
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
)
return@GlobalSearchResultItem
}
GlobalSearchCardRow(
titles = result.result,
getManga = getManga,

View File

@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.manga.components.BaseMangaListItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
@ -18,7 +18,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
fun MigrateMangaScreen(
navigateUp: () -> Unit,
title: String?,
state: MigrateMangaState,
state: MigrateMangaScreenModel.State,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
@ -51,7 +51,7 @@ fun MigrateMangaScreen(
@Composable
private fun MigrateMangaContent(
contentPadding: PaddingValues,
state: MigrateMangaState,
state: MigrateMangaScreenModel.State,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {

View File

@ -1,29 +1,24 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold
@Composable
fun MigrateSearchScreen(
state: SearchScreenModel.State,
fromSourceId: Long?,
navigateUp: () -> Unit,
state: MigrateSearchState,
getManga: @Composable (Manga) -> State<Manga>,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
onChangeSearchFilter: (SourceFilter) -> Unit,
onToggleResults: () -> Unit,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
@ -37,13 +32,17 @@ fun MigrateSearchScreen(
navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
sourceFilter = state.sourceFilter,
onChangeSearchFilter = onChangeSearchFilter,
onlyShowHasResults = state.onlyShowHasResults,
onToggleResults = onToggleResults,
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
MigrateSearchContent(
sourceId = state.manga?.source ?: -1,
items = state.items,
GlobalSearchContent(
fromSourceId = fromSourceId,
items = state.filteredItems,
contentPadding = paddingValues,
getManga = getManga,
onClickSource = onClickSource,
@ -52,50 +51,3 @@ fun MigrateSearchScreen(
)
}
}
@Composable
private fun MigrateSearchContent(
sourceId: Long,
items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
LazyColumn(
contentPadding = contentPadding,
) {
items.forEach { (source, result) ->
item(key = source.id) {
GlobalSearchResultItem(
title = if (source.id == sourceId) "${source.name}" else source.name,
subtitle = LocaleHelper.getDisplayName(source.lang),
onClick = { onClickSource(source) },
) {
when (result) {
SearchItemResult.Loading -> {
GlobalSearchLoadingResultItem()
}
is SearchItemResult.Success -> {
if (result.isEmpty) {
GlobalSearchEmptyResultItem()
return@GlobalSearchResultItem
}
GlobalSearchCardRow(
titles = result.result,
getManga = getManga,
onClick = onClickItem,
onLongClick = onLongClickItem,
)
}
is SearchItemResult.Error -> {
GlobalSearchErrorResultItem(message = result.throwable.message)
}
}
}
}
}
}
}

View File

@ -26,7 +26,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.source.model.Source
import tachiyomi.presentation.core.components.Badge
@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable
fun MigrateSourceScreen(
state: MigrateSourceState,
state: MigrateSourceScreenModel.State,
contentPadding: PaddingValues,
onClickItem: (Source) -> Unit,
onToggleSortingDirection: () -> Unit,

View File

@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterState
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.source.model.Source
import tachiyomi.presentation.core.components.FastScrollLazyColumn
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
@Composable
fun SourcesFilterScreen(
navigateUp: () -> Unit,
state: SourcesFilterState.Success,
state: SourcesFilterScreenModel.State.Success,
onClickLanguage: (String) -> Unit,
onClickSource: (Source) -> Unit,
) {
@ -54,7 +54,7 @@ fun SourcesFilterScreen(
@Composable
private fun SourcesFilterContent(
contentPadding: PaddingValues,
state: SourcesFilterState.Success,
state: SourcesFilterScreenModel.State.Success,
onClickLanguage: (String) -> Unit,
onClickSource: (Source) -> Unit,
) {

View File

@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.SourcesState
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.source.model.Pin
@ -40,7 +40,7 @@ import tachiyomi.source.local.isLocal
@Composable
fun SourcesScreen(
state: SourcesState,
state: SourcesScreenModel.State,
contentPadding: PaddingValues,
onClickItem: (Source, Listing) -> Unit,
onClickPin: (Source) -> Unit,
@ -192,7 +192,7 @@ fun SourceOptionsDialog(
)
}
sealed class SourceUiModel {
data class Item(val source: Source) : SourceUiModel()
data class Header(val language: String) : SourceUiModel()
sealed interface SourceUiModel {
data class Item(val source: Source) : SourceUiModel
data class Header(val language: String) : SourceUiModel
}

View File

@ -142,7 +142,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
}
sealed class Result<out T> {
object Loading : Result<Nothing>()
object Error : Result<Nothing>()
data object Loading : Result<Nothing>()
data object Error : Result<Nothing>()
data class Success<out T>(val value: T) : Result<T>()
}

View File

@ -3,17 +3,21 @@ package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
import eu.kanade.presentation.library.components.MangaComfortableGridItem
import eu.kanade.tachiyomi.R
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.manga.model.asMangaCover
@ -26,13 +30,18 @@ fun GlobalSearchCardRow(
onClick: (Manga) -> Unit,
onLongClick: (Manga) -> Unit,
) {
if (titles.isEmpty()) {
EmptyResultItem()
return
}
LazyRow(
contentPadding = PaddingValues(MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
) {
items(titles) {
val title by getManga(it)
GlobalSearchCard(
MangaItem(
title = title.title,
cover = title.asMangaCover(),
isFavorite = title.favorite,
@ -44,7 +53,7 @@ fun GlobalSearchCardRow(
}
@Composable
private fun GlobalSearchCard(
private fun MangaItem(
title: String,
cover: MangaCover,
isFavorite: Boolean,
@ -54,6 +63,7 @@ private fun GlobalSearchCard(
Box(modifier = Modifier.width(96.dp)) {
MangaComfortableGridItem(
title = title,
titleMaxLines = 3,
coverData = cover,
coverBadgeStart = {
InLibraryBadge(enabled = isFavorite)
@ -64,3 +74,15 @@ private fun GlobalSearchCard(
)
}
}
@Composable
private fun EmptyResultItem() {
Text(
text = stringResource(R.string.no_results_found),
modifier = Modifier
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
)
}

View File

@ -61,18 +61,6 @@ fun GlobalSearchResultItem(
}
}
@Composable
fun GlobalSearchEmptyResultItem() {
Text(
text = stringResource(R.string.no_results_found),
modifier = Modifier
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
)
}
@Composable
fun GlobalSearchLoadingResultItem() {
Box(

View File

@ -1,13 +1,36 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import tachiyomi.presentation.core.components.material.padding
@Composable
fun GlobalSearchToolbar(
@ -17,24 +40,89 @@ fun GlobalSearchToolbar(
navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
sourceFilter: SourceFilter,
onChangeSearchFilter: (SourceFilter) -> Unit,
onlyShowHasResults: Boolean,
onToggleResults: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
) {
Box {
SearchToolbar(
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
onClickCloseSearch = navigateUp,
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
if (progress in 1 until total) {
LinearProgressIndicator(
progress = progress / total.toFloat(),
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth(),
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
Box {
SearchToolbar(
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
onClickCloseSearch = navigateUp,
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
if (progress in 1..<total) {
LinearProgressIndicator(
progress = progress / total.toFloat(),
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth(),
)
}
}
Row(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.padding(horizontal = MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
// TODO: make this UX better; it only applies when triggering a new search
FilterChip(
selected = sourceFilter == SourceFilter.PinnedOnly,
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.PushPin,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.pinned_sources))
},
)
FilterChip(
selected = sourceFilter == SourceFilter.All,
onClick = { onChangeSearchFilter(SourceFilter.All) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.DoneAll,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.all))
},
)
VerticalDivider()
FilterChip(
selected = onlyShowHasResults,
onClick = { onToggleResults() },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.has_results))
},
)
}
HorizontalDivider()
}
}

View File

@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@ -70,6 +71,7 @@ fun NavigatorAdaptiveSheet(
*/
@Composable
fun AdaptiveSheet(
modifier: Modifier = Modifier,
tonalElevation: Dp = 1.dp,
enableSwipeDismiss: Boolean = true,
onDismissRequest: () -> Unit,
@ -82,6 +84,7 @@ fun AdaptiveSheet(
properties = dialogProperties,
) {
AdaptiveSheetImpl(
modifier = modifier,
isTabletUi = isTabletUi,
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss,

View File

@ -13,18 +13,13 @@ import java.util.Date
fun RelativeDateHeader(
modifier: Modifier = Modifier,
date: Date,
relativeTime: Int,
dateFormat: DateFormat,
) {
val context = LocalContext.current
ListGroupHeader(
modifier = modifier,
text = remember {
date.toRelativeString(
context,
relativeTime,
dateFormat,
)
date.toRelativeString(context, dateFormat)
},
)
}

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -30,7 +31,6 @@ import androidx.compose.ui.util.fastForEachIndexed
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.HorizontalPager
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.TabIndicator
object TabbedDialogPaddings {
@ -40,6 +40,7 @@ object TabbedDialogPaddings {
@Composable
fun TabbedDialog(
modifier: Modifier = Modifier,
onDismissRequest: () -> Unit,
tabTitles: List<String>,
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
@ -47,6 +48,7 @@ fun TabbedDialog(
content: @Composable (Int) -> Unit,
) {
AdaptiveSheet(
modifier = modifier,
onDismissRequest = onDismissRequest,
) {
val scope = rememberCoroutineScope()
@ -80,7 +82,7 @@ fun TabbedDialog(
tabOverflowMenuContent?.let { MoreMenu(it) }
}
Divider()
HorizontalDivider()
HorizontalPager(
modifier = Modifier.animateContentSize(),

View File

@ -20,7 +20,6 @@ import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.history.components.HistoryItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import eu.kanade.tachiyomi.ui.history.HistoryState
import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
@ -33,7 +32,7 @@ import java.util.Date
@Composable
fun HistoryScreen(
state: HistoryState,
state: HistoryScreenModel.State,
snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (mangaId: Long) -> Unit,
@ -99,7 +98,6 @@ private fun HistoryScreenContent(
onClickDelete: (HistoryWithRelations) -> Unit,
preferences: UiPreferences = Injekt.get(),
) {
val relativeTime: Int = remember { preferences.relativeTime().get() }
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
FastScrollLazyColumn(
@ -120,7 +118,6 @@ private fun HistoryScreenContent(
RelativeDateHeader(
modifier = Modifier.animateItemPlacement(),
date = item.date,
relativeTime = relativeTime,
dateFormat = dateFormat,
)
}
@ -139,7 +136,7 @@ private fun HistoryScreenContent(
}
}
sealed class HistoryUiModel {
data class Header(val date: Date) : HistoryUiModel()
data class Item(val item: HistoryWithRelations) : HistoryUiModel()
sealed interface HistoryUiModel {
data class Header(val date: Date) : HistoryUiModel
data class Item(val item: HistoryWithRelations) : HistoryUiModel
}

View File

@ -21,12 +21,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.toTimestampString
import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.presentation.core.components.material.padding
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
private val HISTORY_ITEM_HEIGHT = 96.dp
@ -68,7 +67,7 @@ fun HistoryItem(
text = if (history.chapterNumber > -1) {
stringResource(
R.string.recent_manga_time,
chapterFormatter.format(history.chapterNumber),
formatChapterNumber(history.chapterNumber),
readAt,
)
} else {
@ -88,8 +87,3 @@ fun HistoryItem(
}
}
}
private val chapterFormatter = DecimalFormat(
"#.###",
DecimalFormatSymbols().apply { decimalSeparator = '.' },
)

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -15,7 +16,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
import tachiyomi.core.preference.TriState
@ -26,11 +26,11 @@ import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.SettingsFlowRow
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.ChoiceChip
import tachiyomi.presentation.core.util.collectAsState
@Composable
fun LibrarySettingsDialog(
@ -181,12 +181,12 @@ private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel,
) {
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
SettingsFlowRow(R.string.action_display_mode) {
SettingsChipRow(R.string.action_display_mode) {
displayModes.map { (titleRes, mode) ->
ChoiceChip(
isSelected = displayMode == mode,
FilterChip(
selected = displayMode == mode,
onClick = { screenModel.setDisplayMode(mode) },
content = { Text(stringResource(titleRes)) },
label = { Text(stringResource(titleRes)) },
)
}
}
@ -211,59 +211,35 @@ private fun ColumnScope.DisplayPage(
} else {
stringResource(R.string.label_default)
},
onChange = { columnPreference.set(it) },
onChange = columnPreference::set,
)
}
HeadingItem(R.string.overlay_header)
val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_download_badge),
checked = downloadBadge,
onClick = {
screenModel.togglePreference(LibraryPreferences::downloadBadge)
},
pref = screenModel.libraryPreferences.downloadBadge(),
)
val localBadge by screenModel.libraryPreferences.localBadge().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_local_badge),
checked = localBadge,
onClick = {
screenModel.togglePreference(LibraryPreferences::localBadge)
},
pref = screenModel.libraryPreferences.localBadge(),
)
val languageBadge by screenModel.libraryPreferences.languageBadge().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_language_badge),
checked = languageBadge,
onClick = {
screenModel.togglePreference(LibraryPreferences::languageBadge)
},
pref = screenModel.libraryPreferences.languageBadge(),
)
val showContinueReadingButton by screenModel.libraryPreferences.showContinueReadingButton().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_show_continue_reading_button),
checked = showContinueReadingButton,
onClick = {
screenModel.togglePreference(LibraryPreferences::showContinueReadingButton)
},
pref = screenModel.libraryPreferences.showContinueReadingButton(),
)
HeadingItem(R.string.tabs_header)
val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_show_tabs),
checked = categoryTabs,
onClick = {
screenModel.togglePreference(LibraryPreferences::categoryTabs)
},
pref = screenModel.libraryPreferences.categoryTabs(),
)
val categoryNumberOfItems by screenModel.libraryPreferences.categoryNumberOfItems().collectAsState()
CheckboxItem(
label = stringResource(R.string.action_display_show_number_of_items),
checked = categoryNumberOfItems,
onClick = {
screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems)
},
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
)
}

View File

@ -163,6 +163,7 @@ private fun BoxScope.CoverTextOverlay(
fun MangaComfortableGridItem(
isSelected: Boolean = false,
title: String,
titleMaxLines: Int = 2,
coverData: tachiyomi.domain.manga.model.MangaCover,
coverAlpha: Float = 1f,
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
@ -204,6 +205,7 @@ fun MangaComfortableGridItem(
title = title,
style = MaterialTheme.typography.titleSmall,
minLines = 2,
maxLines = titleMaxLines,
)
}
}
@ -253,6 +255,7 @@ private fun GridItemTitle(
title: String,
style: TextStyle,
minLines: Int,
maxLines: Int = 2,
) {
Text(
modifier = modifier,
@ -260,7 +263,7 @@ private fun GridItemTitle(
fontSize = 12.sp,
lineHeight = 18.sp,
minLines = minLines,
maxLines = 2,
maxLines = maxLines,
overflow = TextOverflow.Ellipsis,
style = style,
)

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
@ -9,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.category.visualName
import tachiyomi.domain.category.model.Category
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.TabIndicator
import tachiyomi.presentation.core.components.material.TabText
@ -44,6 +44,6 @@ internal fun LibraryTabs(
}
}
Divider()
HorizontalDivider()
}
}

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.manga
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
@ -8,6 +9,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
@Composable
@ -18,15 +20,27 @@ fun DuplicateMangaDialog(
) {
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
confirmButton = {
Row {
TextButton(onClick = {
onDismissRequest()
onOpenManga()
},) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TextButton(
onClick = {
onDismissRequest()
onOpenManga()
},
) {
Text(text = stringResource(R.string.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel))
}
@ -40,11 +54,5 @@ fun DuplicateMangaDialog(
}
}
},
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
)
}

View File

@ -57,12 +57,12 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
import eu.kanade.presentation.manga.components.MangaChapterListItem
import eu.kanade.presentation.manga.components.MangaInfoBox
import eu.kanade.presentation.manga.components.MangaToolbar
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.ui.manga.chapterDecimalFormat
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.chapter.model.Chapter
@ -82,9 +82,9 @@ import java.util.Date
@Composable
fun MangaScreen(
state: MangaScreenState.Success,
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
fetchInterval: Int?,
dateFormat: DateFormat,
isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
@ -112,6 +112,7 @@ fun MangaScreen(
onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?,
onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
// For bottom action menu
@ -139,8 +140,8 @@ fun MangaScreen(
MangaScreenSmallImpl(
state = state,
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
fetchInterval = fetchInterval,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked,
@ -160,6 +161,7 @@ fun MangaScreen(
onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -174,10 +176,10 @@ fun MangaScreen(
MangaScreenLargeImpl(
state = state,
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat,
fetchInterval = fetchInterval,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
@ -195,6 +197,7 @@ fun MangaScreen(
onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -210,10 +213,10 @@ fun MangaScreen(
@Composable
private fun MangaScreenSmallImpl(
state: MangaScreenState.Success,
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
@ -240,6 +243,7 @@ private fun MangaScreenSmallImpl(
onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
// For bottom action menu
@ -279,9 +283,11 @@ private fun MangaScreenSmallImpl(
}
val animatedTitleAlpha by animateFloatAsState(
if (firstVisibleItemIndex > 0) 1f else 0f,
label = "titleAlpha",
)
val animatedBgAlpha by animateFloatAsState(
if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f,
label = "bgAlpha",
)
MangaToolbar(
title = state.manga.title,
@ -383,10 +389,13 @@ private fun MangaScreenSmallImpl(
MangaActionRow(
favorite = state.manga.favorite,
trackingCount = state.trackingCount,
fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked,
onTrackingClicked = onTrackingClicked,
onEditIntervalClicked = onEditIntervalClicked,
onEditCategory = onEditCategoryClicked,
)
}
@ -419,7 +428,6 @@ private fun MangaScreenSmallImpl(
sharedChapterItems(
manga = state.manga,
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
@ -436,10 +444,10 @@ private fun MangaScreenSmallImpl(
@Composable
fun MangaScreenLargeImpl(
state: MangaScreenState.Success,
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
@ -466,6 +474,7 @@ fun MangaScreenLargeImpl(
onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
// For bottom action menu
@ -596,10 +605,13 @@ fun MangaScreenLargeImpl(
MangaActionRow(
favorite = state.manga.favorite,
trackingCount = state.trackingCount,
fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked,
onTrackingClicked = onTrackingClicked,
onEditIntervalClicked = onEditIntervalClicked,
onEditCategory = onEditCategoryClicked,
)
ExpandableMangaDescription(
@ -639,7 +651,6 @@ fun MangaScreenLargeImpl(
sharedChapterItems(
manga = state.manga,
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
@ -701,7 +712,6 @@ private fun SharedMangaBottomActionMenu(
private fun LazyListScope.sharedChapterItems(
manga: Manga,
chapters: List<ChapterItem>,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -722,7 +732,7 @@ private fun LazyListScope.sharedChapterItems(
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
stringResource(
R.string.display_mode_chapter,
chapterDecimalFormat.format(chapterItem.chapter.chapterNumber.toDouble()),
formatChapterNumber(chapterItem.chapter.chapterNumber),
)
} else {
chapterItem.chapter.name
@ -730,11 +740,7 @@ private fun LazyListScope.sharedChapterItems(
date = chapterItem.chapter.dateUpload
.takeIf { it > 0L }
?.let {
Date(it).toRelativeString(
context,
dateRelativeTime,
dateFormat,
)
Date(it).toRelativeString(context, dateFormat)
},
readProgress = chapterItem.chapter.lastPageRead
.takeIf { !chapterItem.chapter.read && it > 0L }

View File

@ -88,7 +88,7 @@ fun MangaBottomActionMenu(
var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0 until 7).forEach { i -> confirm[i] = i == toConfirmIndex }
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1.seconds)
@ -241,7 +241,7 @@ fun LibraryBottomActionMenu(
var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex }
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1.seconds)

View File

@ -1,11 +1,23 @@
package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
import tachiyomi.presentation.core.components.WheelTextPicker
@Composable
fun DeleteChaptersDialog(
@ -37,3 +49,51 @@ fun DeleteChaptersDialog(
},
)
}
@Composable
fun SetIntervalDialog(
interval: Int,
onDismissRequest: () -> Unit,
onValueChanged: (Int) -> Unit,
) {
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.manga_modify_calculated_interval_title)) },
text = {
BoxWithConstraints(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
val size = DpSize(width = maxWidth / 2, height = 128.dp)
val items = (0..MAX_FETCH_INTERVAL).map {
if (it == 0) {
stringResource(R.string.label_default)
} else {
it.toString()
}
}
WheelTextPicker(
size = size,
items = items,
startIndex = selectedInterval,
onSelectionChanged = { selectedInterval = it },
)
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel))
}
},
confirmButton = {
TextButton(onClick = {
onValueChanged(selectedInterval)
onDismissRequest()
},) {
Text(text = stringResource(R.string.action_ok))
}
},
)
}

View File

@ -25,6 +25,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.HourglassEmpty
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.outlined.Block
@ -43,7 +44,6 @@ import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@ -84,6 +84,7 @@ import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@ -165,14 +166,18 @@ fun MangaActionRow(
modifier: Modifier = Modifier,
favorite: Boolean,
trackingCount: Int,
fetchInterval: Int?,
isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onEditCategory: (() -> Unit)?,
) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
MangaActionButton(
title = if (favorite) {
stringResource(R.string.in_library)
@ -184,6 +189,14 @@ fun MangaActionRow(
onClick = onAddToLibraryClicked,
onLongClick = onEditCategory,
)
if (onEditIntervalClicked != null && fetchInterval != null) {
MangaActionButton(
title = pluralStringResource(id = R.plurals.day, count = fetchInterval.absoluteValue, fetchInterval.absoluteValue),
icon = Icons.Default.HourglassEmpty,
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
onClick = onEditIntervalClicked,
)
}
if (onTrackingClicked != null) {
MangaActionButton(
title = if (trackingCount == 0) {
@ -652,11 +665,6 @@ private fun TagsChip(
modifier = modifier,
onClick = onClick,
label = { Text(text = text, style = MaterialTheme.typography.bodySmall) },
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
labelColor = MaterialTheme.colorScheme.onSurface,
),
)
}
}

View File

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@ -12,7 +13,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.Divider
@Composable
fun LogoHeader() {
@ -29,6 +29,6 @@ fun LogoHeader() {
.size(64.dp),
)
Divider()
HorizontalDivider()
}
}

View File

@ -17,6 +17,7 @@ import androidx.compose.material.icons.outlined.Label
import androidx.compose.material.icons.outlined.QueryStats
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.SettingsBackupRestore
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
@ -31,7 +32,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import tachiyomi.core.Constants
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
@Composable
@ -94,7 +94,7 @@ fun MoreScreen(
)
}
item { Divider() }
item { HorizontalDivider() }
item {
val downloadQueueState = downloadQueueStateProvider()
@ -147,7 +147,7 @@ fun MoreScreen(
)
}
item { Divider() }
item { HorizontalDivider() }
item {
TextPreferenceWidget(

View File

@ -13,8 +13,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.InfoWidget
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
@ -22,10 +20,10 @@ import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidg
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
import eu.kanade.presentation.util.collectAsState
import kotlinx.coroutines.launch
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -158,16 +156,6 @@ internal fun PreferenceItem(
},
)
}
is Preference.PreferenceItem.AppThemePreference -> {
val value by item.pref.collectAsState()
val amoled by Injekt.get<UiPreferences>().themeDarkAmoled().collectAsState()
AppThemePreferenceWidget(
title = item.title,
value = value,
amoled = amoled,
onItemClick = { scope.launch { item.pref.set(it) } },
)
}
is Preference.PreferenceItem.TrackingPreference -> {
val uName by Injekt.get<PreferenceStore>()
.getString(TrackPreferences.trackUsername(item.service.id))
@ -183,6 +171,9 @@ internal fun PreferenceItem(
is Preference.PreferenceItem.InfoPreference -> {
InfoWidget(text = item.title)
}
is Preference.PreferenceItem.CustomPreference -> {
item.content(item)
}
}
}
}

View File

@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
@ -133,19 +132,6 @@ sealed class Preference {
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
) : PreferenceItem<String>()
/**
* A [PreferenceItem] that shows previews of [AppTheme] selection.
*/
data class AppThemePreference(
val pref: PreferenceData<AppTheme>,
override val title: String,
) : PreferenceItem<AppTheme>() {
override val enabled: Boolean = true
override val subtitle: String? = null
override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: AppTheme) -> Boolean = { true }
}
/**
* A [PreferenceItem] for individual tracking service.
*/
@ -169,6 +155,16 @@ sealed class Preference {
override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
}
data class CustomPreference(
override val title: String,
val content: @Composable (PreferenceItem<String>) -> Unit,
) : PreferenceItem<String>() {
override val enabled: Boolean = true
override val subtitle: String? = null
override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
}
}
data class PreferenceGroup(

View File

@ -30,7 +30,6 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache
@ -65,6 +64,7 @@ import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -114,7 +114,7 @@ object SettingsAdvancedScreen : SearchableSettings {
),
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_debug_info),
onClick = { navigator.push(DebugInfoScreen) },
onClick = { navigator.push(DebugInfoScreen()) },
),
),
)
@ -221,7 +221,10 @@ object SettingsAdvancedScreen : SearchableSettings {
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_invalidate_download_cache),
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
onClick = { Injekt.get<DownloadCache>().invalidateCache() },
onClick = {
Injekt.get<DownloadCache>().invalidateCache()
context.toast(R.string.download_cache_invalidated)
},
),
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_clear_database),

View File

@ -21,7 +21,7 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.merge
import org.xmlpull.v1.XmlPullParser
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
@ -48,7 +49,6 @@ object SettingsAppearanceScreen : SearchableSettings {
return listOf(
getThemeGroup(context = context, uiPreferences = uiPreferences),
getDisplayGroup(context = context, uiPreferences = uiPreferences),
getTimestampGroup(uiPreferences = uiPreferences),
)
}
@ -59,8 +59,11 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
LaunchedEffect(themeMode) {
setAppCompatDelegateThemeMode(themeMode)
@ -91,10 +94,17 @@ object SettingsAppearanceScreen : SearchableSettings {
)
},
),
Preference.PreferenceItem.AppThemePreference(
Preference.PreferenceItem.CustomPreference(
title = stringResource(R.string.pref_app_theme),
pref = appThemePref,
),
) { item ->
val value by appThemePref.collectAsState()
AppThemePreferenceWidget(
title = item.title,
value = value,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
},
Preference.PreferenceItem.SwitchPreference(
pref = amoledPref,
title = stringResource(R.string.pref_dark_theme_pure_black),
@ -111,6 +121,7 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup {
val langs = remember { getLangs(context) }
var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") }
val now = remember { Date().time }
LaunchedEffect(currentLanguage) {
val locale = if (currentLanguage.isEmpty()) {
@ -136,31 +147,12 @@ object SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.tabletUiMode(),
title = stringResource(R.string.pref_tablet_ui_mode),
entries = TabletUiMode.values().associateWith { stringResource(it.titleResId) },
entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
onValueChanged = {
context.toast(R.string.requires_app_restart)
true
},
),
),
)
}
@Composable
private fun getTimestampGroup(uiPreferences: UiPreferences): Preference.PreferenceGroup {
val now = remember { Date().time }
return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_timestamps),
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.relativeTime(),
title = stringResource(R.string.pref_relative_format),
entries = mapOf(
0 to stringResource(R.string.off),
2 to stringResource(R.string.pref_relative_time_short),
7 to stringResource(R.string.pref_relative_time_long),
),
),
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.dateFormat(),
title = stringResource(R.string.pref_date_format),
@ -173,14 +165,13 @@ object SettingsAppearanceScreen : SearchableSettings {
),
)
}
private fun getLangs(context: Context): Map<String, String> {
val langs = mutableListOf<Pair<String, String>>()
val parser = context.resources.getXml(R.xml.locales_config)
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
for (i in 0 until parser.attributeCount) {
for (i in 0..<parser.attributeCount) {
if (parser.getAttributeName(i) == "name") {
val langTag = parser.getAttributeValue(i)
val displayName = LocaleHelper.getDisplayName(langTag)

View File

@ -21,6 +21,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -43,7 +44,6 @@ import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.presentation.extensions.RequestStoragePermission
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
@ -60,7 +60,7 @@ import kotlinx.coroutines.launch
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.sync.SyncPreferences
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart
import uy.kohesive.injekt.Injekt
@ -280,8 +280,8 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
}
}
}
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}
},
dismissButton = {

View File

@ -22,12 +22,12 @@ import com.hippo.unifile.UniFile
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File

View File

@ -1,33 +1,18 @@
package eu.kanade.presentation.more.settings.screen
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMap
import androidx.core.content.ContextCompat
import cafe.adriel.voyager.navigator.LocalNavigator
@ -36,12 +21,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.util.system.isDevFlavor
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories
@ -56,8 +39,7 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD
import tachiyomi.presentation.core.components.WheelTextPicker
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -142,13 +124,10 @@ object SettingsLibraryScreen : SearchableSettings {
val context = LocalContext.current
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
val libraryUpdateDeviceRestrictionPref = libraryPreferences.libraryUpdateDeviceRestriction()
val libraryUpdateMangaRestrictionPref = libraryPreferences.libraryUpdateMangaRestriction()
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
val included by libraryUpdateCategoriesPref.collectAsState()
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
@ -169,25 +148,10 @@ object SettingsLibraryScreen : SearchableSettings {
},
)
}
val leadRange by libraryPreferences.leadingExpectedDays().collectAsState()
val followRange by libraryPreferences.followingExpectedDays().collectAsState()
var showFetchRangesDialog by rememberSaveable { mutableStateOf(false) }
if (showFetchRangesDialog) {
LibraryExpectedRangeDialog(
initialLead = leadRange,
initialFollow = followRange,
onDismissRequest = { showFetchRangesDialog = false },
onValueChanged = { leadValue, followValue ->
libraryPreferences.leadingExpectedDays().set(leadValue)
libraryPreferences.followingExpectedDays().set(followValue)
showFetchRangesDialog = false
},
)
}
return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_library_update),
preferenceItems = listOfNotNull(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = libraryUpdateIntervalPref,
title = stringResource(R.string.pref_library_update_interval),
@ -205,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
},
),
Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryUpdateDeviceRestrictionPref,
pref = libraryPreferences.libraryUpdateDeviceRestriction(),
enabled = libraryUpdateInterval > 0,
title = stringResource(R.string.pref_library_update_restriction),
subtitle = stringResource(R.string.restrictions),
@ -241,30 +205,16 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(R.string.pref_library_update_refresh_trackers),
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
),
// TODO: remove isDevFlavor checks once functionality is available
Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryUpdateMangaRestrictionPref,
pref = libraryPreferences.libraryUpdateMangaRestriction(),
title = stringResource(R.string.pref_library_update_manga_restriction),
entries = buildMap {
put(MANGA_HAS_UNREAD, stringResource(R.string.pref_update_only_completely_read))
put(MANGA_NON_READ, stringResource(R.string.pref_update_only_started))
put(MANGA_NON_COMPLETED, stringResource(R.string.pref_update_only_non_completed))
if (isDevFlavor) {
put(MANGA_OUTSIDE_RELEASE_PERIOD, stringResource(R.string.pref_update_only_in_release_period))
}
},
entries = mapOf(
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_update_release_grace_period),
subtitle = listOf(
pluralStringResource(R.plurals.pref_update_release_leading_days, leadRange, leadRange),
pluralStringResource(R.plurals.pref_update_release_following_days, followRange, followRange),
).joinToString(),
onClick = { showFetchRangesDialog = true },
).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction && isDevFlavor },
Preference.PreferenceItem.InfoPreference(
title = stringResource(R.string.pref_update_release_grace_period_info),
).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction && isDevFlavor },
Preference.PreferenceItem.SwitchPreference(
pref = libraryPreferences.newShowUpdatesCount(),
title = stringResource(R.string.pref_library_update_show_tab_badge),
@ -303,79 +253,4 @@ object SettingsLibraryScreen : SearchableSettings {
),
)
}
@Composable
private fun LibraryExpectedRangeDialog(
initialLead: Int,
initialFollow: Int,
onDismissRequest: () -> Unit,
onValueChanged: (portrait: Int, landscape: Int) -> Unit,
) {
var leadValue by rememberSaveable { mutableIntStateOf(initialLead) }
var followValue by rememberSaveable { mutableIntStateOf(initialFollow) }
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.pref_update_release_grace_period)) },
text = {
Column {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.weight(1f),
text = pluralStringResource(R.plurals.pref_update_release_leading_days, leadValue, leadValue),
textAlign = TextAlign.Center,
maxLines = 1,
style = MaterialTheme.typography.labelMedium,
)
Text(
modifier = Modifier.weight(1f),
text = pluralStringResource(R.plurals.pref_update_release_following_days, followValue, followValue),
textAlign = TextAlign.Center,
maxLines = 1,
style = MaterialTheme.typography.labelMedium,
)
}
}
BoxWithConstraints(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
val size = DpSize(width = maxWidth / 2, height = 128.dp)
val items = (0..MAX_GRACE_PERIOD).map(Int::toString)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
WheelTextPicker(
size = size,
items = items,
startIndex = leadValue,
onSelectionChanged = {
leadValue = it
},
)
WheelTextPicker(
size = size,
items = items,
startIndex = followValue,
onSelectionChanged = {
followValue = it
},
)
}
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(android.R.string.cancel))
}
},
confirmButton = {
TextButton(onClick = { onValueChanged(leadValue, followValue) }) {
Text(text = stringResource(R.string.action_ok))
}
},
)
}
}

View File

@ -9,12 +9,12 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.NumberFormat
@ -33,7 +33,7 @@ object SettingsReaderScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = readerPref.defaultReadingMode(),
title = stringResource(R.string.pref_viewer_type),
entries = ReadingModeType.values().drop(1)
entries = ReadingModeType.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) },
),
Preference.PreferenceItem.ListPreference(
@ -84,7 +84,7 @@ object SettingsReaderScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = readerPreferences.defaultOrientationType(),
title = stringResource(R.string.pref_rotation_type),
entries = OrientationType.values().drop(1)
entries = OrientationType.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) },
),
Preference.PreferenceItem.ListPreference(

View File

@ -17,6 +17,7 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -53,7 +54,6 @@ import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
@ -139,7 +139,7 @@ class SettingsSearchScreen : Screen() {
}
},
)
Divider()
HorizontalDivider()
}
},
) { contentPadding ->

View File

@ -10,11 +10,11 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentActivity
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -70,7 +70,7 @@ object SettingsSecurityScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(R.string.secure_screen),
entries = SecurityPreferences.SecureScreenMode.values()
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleResId) },
),
Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)),

View File

@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.HtmlCompat
@ -45,6 +46,7 @@ class OpenSourceLibraryLicenseScreen(
Text(
text = name,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
@ -14,10 +15,12 @@ import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
@ -47,8 +50,6 @@ import tachiyomi.data.Database
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
import tachiyomi.domain.source.model.Source
import tachiyomi.domain.source.model.SourceWithCount
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen
@ -135,7 +136,7 @@ class ClearDatabaseScreen : Screen() {
.padding(contentPadding)
.fillMaxSize(),
) {
FastScrollLazyColumn(
LazyColumn(
modifier = Modifier.weight(1f),
) {
items(s.items) { sourceWithCount ->
@ -148,7 +149,7 @@ class ClearDatabaseScreen : Screen() {
}
}
Divider()
HorizontalDivider()
Button(
modifier = Modifier
@ -269,12 +270,15 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
state.copy(showConfirmation = false)
}
sealed class State {
object Loading : State()
sealed interface State {
@Immutable
data object Loading : State
@Immutable
data class Ready(
val items: List<SourceWithCount>,
val selection: List<Long> = emptyList(),
val showConfirmation: Boolean = false,
) : State()
) : State
}
}

View File

@ -28,9 +28,11 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
import tachiyomi.presentation.core.components.material.Scaffold
object BackupSchemaScreen : Screen() {
class BackupSchemaScreen : Screen() {
const val title = "Backup file schema"
companion object {
const val title = "Backup file schema"
}
@Composable
override fun Content() {

View File

@ -18,7 +18,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil
import kotlinx.coroutines.guava.await
object DebugInfoScreen : Screen() {
class DebugInfoScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
@ -29,11 +30,11 @@ object DebugInfoScreen : Screen() {
listOf(
Preference.PreferenceItem.TextPreference(
title = WorkerInfoScreen.title,
onClick = { navigator.push(WorkerInfoScreen) },
onClick = { navigator.push(WorkerInfoScreen()) },
),
Preference.PreferenceItem.TextPreference(
title = BackupSchemaScreen.title,
onClick = { navigator.push(BackupSchemaScreen) },
onClick = { navigator.push(BackupSchemaScreen()) },
),
getAppInfoGroup(),
getDeviceInfoGroup(),
@ -67,11 +68,15 @@ object DebugInfoScreen : Screen() {
@Composable
@ReadOnlyComposable
private fun getWebViewVersion(): String {
val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?"
val pm = LocalContext.current.packageManager
val label = webView.applicationInfo.loadLabel(pm)
val version = webView.versionName
return "$label $version"
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?"
val pm = LocalContext.current.packageManager
val label = webView.applicationInfo.loadLabel(pm)
val version = webView.versionName
return "$label $version"
} else {
return "Unknown"
}
}
@Composable

View File

@ -43,9 +43,11 @@ import kotlinx.coroutines.flow.stateIn
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.util.plus
object WorkerInfoScreen : Screen() {
class WorkerInfoScreen : Screen() {
const val title = "Worker info"
companion object {
const val title = "Worker info"
}
@Composable
override fun Content() {

View File

@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -44,7 +45,6 @@ import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.ThemePreviews
import tachiyomi.presentation.core.util.secondaryItemAlpha
@ -75,7 +75,7 @@ private fun AppThemesList(
onItemClick: (AppTheme) -> Unit,
) {
val appThemes = remember {
AppTheme.values()
AppTheme.entries
.filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
}
LazyRow(
@ -123,7 +123,6 @@ fun AppThemePreviewItem(
selected: Boolean,
onClick: () -> Unit,
) {
val dividerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA)
Column(
modifier = Modifier
.fillMaxWidth()
@ -133,7 +132,7 @@ fun AppThemePreviewItem(
color = if (selected) {
MaterialTheme.colorScheme.primary
} else {
dividerColor
DividerDefaults.color
},
shape = RoundedCornerShape(17.dp),
)
@ -180,7 +179,7 @@ fun AppThemePreviewItem(
modifier = Modifier
.padding(start = 8.dp, top = 2.dp)
.background(
color = dividerColor,
color = DividerDefaults.color,
shape = MaterialTheme.shapes.small,
)
.fillMaxWidth(0.5f)

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
@ -25,7 +26,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart
@ -69,8 +69,8 @@ fun <T> ListPreferenceWidget(
}
}
}
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}
},
confirmButton = {

View File

@ -15,6 +15,7 @@ import androidx.compose.material.icons.rounded.CheckBox
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
import androidx.compose.material.icons.rounded.DisabledByDefault
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@ -29,7 +30,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart
@ -115,8 +115,8 @@ fun <T> TriStateListDialog(
}
}
if (!listState.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
if (!listState.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
if (!listState.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!listState.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}
}
},

View File

@ -3,9 +3,9 @@ package eu.kanade.presentation.more.stats
import androidx.compose.runtime.Immutable
import eu.kanade.presentation.more.stats.data.StatsData
sealed class StatsScreenState {
sealed interface StatsScreenState {
@Immutable
object Loading : StatsScreenState()
data object Loading : StatsScreenState
@Immutable
data class Success(
@ -13,5 +13,5 @@ sealed class StatsScreenState {
val titles: StatsData.Titles,
val chapters: StatsData.Chapters,
val trackers: StatsData.Trackers,
) : StatsScreenState()
) : StatsScreenState
}

View File

@ -1,28 +1,28 @@
package eu.kanade.presentation.more.stats.data
sealed class StatsData {
sealed interface StatsData {
data class Overview(
val libraryMangaCount: Int,
val completedMangaCount: Int,
val totalReadDuration: Long,
) : StatsData()
) : StatsData
data class Titles(
val globalUpdateItemCount: Int,
val startedMangaCount: Int,
val localMangaCount: Int,
) : StatsData()
) : StatsData
data class Chapters(
val totalChapterCount: Int,
val readChapterCount: Int,
val downloadCount: Int,
) : StatsData()
) : StatsData
data class Trackers(
val trackedTitleCount: Int,
val meanScore: Double,
val trackerCount: Int,
) : StatsData()
) : StatsData
}

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
import android.os.Build
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -10,15 +11,13 @@ import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.core.preference.getAndSet
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SettingsFlowRow
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.components.material.ChoiceChip
import tachiyomi.presentation.core.util.collectAsState
@Composable
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
@ -44,10 +43,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_custom_brightness),
checked = customBrightness,
onClick = {
screenModel.togglePreference(ReaderPreferences::customBrightness)
},
pref = screenModel.preferences.customBrightness(),
)
/**
@ -71,10 +67,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_custom_color_filter),
checked = colorFilter,
onClick = {
screenModel.togglePreference(ReaderPreferences::colorFilter)
},
pref = screenModel.preferences.colorFilter(),
)
if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
@ -124,32 +117,24 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
)
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
SettingsFlowRow(R.string.pref_color_filter_mode) {
SettingsChipRow(R.string.pref_color_filter_mode) {
colorFilterModes.mapIndexed { index, it ->
ChoiceChip(
isSelected = colorFilterMode == index,
FilterChip(
selected = colorFilterMode == index,
onClick = { screenModel.preferences.colorFilterMode().set(index) },
content = { Text(it) },
label = { Text(it) },
)
}
}
}
val grayscale by screenModel.preferences.grayscale().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_grayscale),
checked = grayscale,
onClick = {
screenModel.togglePreference(ReaderPreferences::grayscale)
},
pref = screenModel.preferences.grayscale(),
)
val invertedColors by screenModel.preferences.invertedColors().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_inverted_colors),
checked = invertedColors,
onClick = {
screenModel.togglePreference(ReaderPreferences::invertedColors)
},
pref = screenModel.preferences.invertedColors(),
)
}

View File

@ -1,17 +1,16 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SettingsFlowRow
import tachiyomi.presentation.core.components.material.ChoiceChip
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.util.collectAsState
private val themes = listOf(
R.string.black_background to 1,
@ -23,77 +22,49 @@ private val themes = listOf(
@Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
SettingsFlowRow(R.string.pref_reader_theme) {
SettingsChipRow(R.string.pref_reader_theme) {
themes.map { (labelRes, value) ->
ChoiceChip(
isSelected = readerTheme == value,
FilterChip(
selected = readerTheme == value,
onClick = { screenModel.preferences.readerTheme().set(value) },
content = { Text(stringResource(labelRes)) },
label = { Text(stringResource(labelRes)) },
)
}
}
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_show_page_number),
checked = showPageNumber,
onClick = {
screenModel.togglePreference(ReaderPreferences::showPageNumber)
},
pref = screenModel.preferences.showPageNumber(),
)
val fullscreen by screenModel.preferences.fullscreen().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_fullscreen),
checked = fullscreen,
onClick = {
screenModel.togglePreference(ReaderPreferences::fullscreen)
},
pref = screenModel.preferences.fullscreen(),
)
// TODO: hide if there's no cutout
val cutoutShort by screenModel.preferences.cutoutShort().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_cutout_short),
checked = cutoutShort,
onClick = {
screenModel.togglePreference(ReaderPreferences::cutoutShort)
},
pref = screenModel.preferences.cutoutShort(),
)
val keepScreenOn by screenModel.preferences.keepScreenOn().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_keep_screen_on),
checked = keepScreenOn,
onClick = {
screenModel.togglePreference(ReaderPreferences::keepScreenOn)
},
pref = screenModel.preferences.keepScreenOn(),
)
val readWithLongTap by screenModel.preferences.readWithLongTap().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_read_with_long_tap),
checked = readWithLongTap,
onClick = {
screenModel.togglePreference(ReaderPreferences::readWithLongTap)
},
pref = screenModel.preferences.readWithLongTap(),
)
val alwaysShowChapterTransition by screenModel.preferences.alwaysShowChapterTransition().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_always_show_chapter_transition),
checked = alwaysShowChapterTransition,
onClick = {
screenModel.togglePreference(ReaderPreferences::alwaysShowChapterTransition)
},
pref = screenModel.preferences.alwaysShowChapterTransition(),
)
val pageTransitions by screenModel.preferences.pageTransitions().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_page_transitions),
checked = pageTransitions,
onClick = {
screenModel.togglePreference(ReaderPreferences::pageTransitions)
},
pref = screenModel.preferences.pageTransitions(),
)
}

View File

@ -1,6 +1,8 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
@ -30,35 +32,38 @@ fun ReaderSettingsDialog(
)
val pagerState = rememberPagerState { tabTitles.size }
TabbedDialog(
onDismissRequest = {
onDismissRequest()
onShowMenus()
},
tabTitles = tabTitles,
pagerState = pagerState,
) { page ->
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage == 2) {
window?.setDimAmount(0f)
onHideMenus()
} else {
window?.setDimAmount(0.5f)
BoxWithConstraints {
TabbedDialog(
modifier = Modifier.heightIn(max = maxHeight * 0.75f),
onDismissRequest = {
onDismissRequest()
onShowMenus()
}
}
},
tabTitles = tabTitles,
pagerState = pagerState,
) { page ->
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
when (page) {
0 -> ReadingModePage(screenModel)
1 -> GeneralPage(screenModel)
2 -> ColorFilterPage(screenModel)
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage == 2) {
window?.setDimAmount(0f)
onHideMenus()
} else {
window?.setDimAmount(0.5f)
onShowMenus()
}
}
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
when (page) {
0 -> ReadingModePage(screenModel)
1 -> GeneralPage(screenModel)
2 -> ColorFilterPage(screenModel)
}
}
}
}

View File

@ -1,6 +1,8 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -8,7 +10,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@ -18,13 +19,14 @@ import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.SelectItem
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState
import java.text.NumberFormat
private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it }
private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it }
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it }
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it }
@Composable
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
@ -32,21 +34,25 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
val manga by screenModel.mangaFlow.collectAsState()
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
SelectItem(
label = stringResource(R.string.pref_category_reading_mode),
options = readingModeOptions.map { stringResource(it.first) }.toTypedArray(),
selectedIndex = readingModeOptions.indexOfFirst { it.second == readingMode },
) {
screenModel.onChangeReadingMode(readingModeOptions[it].second)
SettingsChipRow(R.string.pref_category_reading_mode) {
readingModeOptions.map { (stringRes, it) ->
FilterChip(
selected = it == readingMode,
onClick = { screenModel.onChangeReadingMode(it) },
label = { Text(stringResource(stringRes)) },
)
}
}
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
SelectItem(
label = stringResource(R.string.rotation_type),
options = orientationTypeOptions.map { stringResource(it.first) }.toTypedArray(),
selectedIndex = orientationTypeOptions.indexOfFirst { it.second == orientationType },
) {
screenModel.onChangeOrientation(orientationTypeOptions[it].second)
SettingsChipRow(R.string.rotation_type) {
orientationTypeOptions.map { (stringRes, it) ->
FilterChip(
selected = it == orientationType,
onClick = { screenModel.onChangeOrientation(it) },
label = { Text(stringResource(stringRes)) },
)
}
}
val viewer by screenModel.viewerFlow.collectAsState()
@ -62,105 +68,74 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
HeadingItem(R.string.pager_viewer)
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
SelectItem(
label = stringResource(R.string.pref_viewer_nav),
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
selectedIndex = navigationModePager,
onSelect = { screenModel.preferences.navigationModePager().set(it) },
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
TapZonesItems(
selected = navigationModePager,
onSelect = screenModel.preferences.navigationModePager()::set,
invertMode = pagerNavInverted,
onSelectInvertMode = screenModel.preferences.pagerNavInverted()::set,
)
if (navigationModePager != 5) {
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
SelectItem(
label = stringResource(R.string.pref_read_with_tapping_inverted),
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == pagerNavInverted },
onSelect = {
screenModel.preferences.pagerNavInverted().set(tappingInvertModeOptions[it].second)
},
)
}
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
SelectItem(
label = stringResource(R.string.pref_image_scale_type),
options = ReaderPreferences.ImageScaleType.map { stringResource(it) }.toTypedArray(),
selectedIndex = imageScaleType - 1,
onSelect = { screenModel.preferences.imageScaleType().set(it + 1) },
)
SettingsChipRow(R.string.pref_image_scale_type) {
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
FilterChip(
selected = imageScaleType == index + 1,
onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
label = { Text(stringResource(it)) },
)
}
}
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
SelectItem(
label = stringResource(R.string.pref_zoom_start),
options = ReaderPreferences.ZoomStart.map { stringResource(it) }.toTypedArray(),
selectedIndex = zoomStart - 1,
onSelect = { screenModel.preferences.zoomStart().set(it + 1) },
)
SettingsChipRow(R.string.pref_zoom_start) {
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
FilterChip(
selected = zoomStart == index + 1,
onClick = { screenModel.preferences.zoomStart().set(index + 1) },
label = { Text(stringResource(it)) },
)
}
}
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_crop_borders),
checked = cropBorders,
onClick = {
screenModel.togglePreference(ReaderPreferences::cropBorders)
},
pref = screenModel.preferences.cropBorders(),
)
val landscapeZoom by screenModel.preferences.landscapeZoom().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_landscape_zoom),
checked = landscapeZoom,
onClick = {
screenModel.togglePreference(ReaderPreferences::landscapeZoom)
},
pref = screenModel.preferences.landscapeZoom(),
)
val navigateToPan by screenModel.preferences.navigateToPan().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_navigate_pan),
checked = navigateToPan,
onClick = {
screenModel.togglePreference(ReaderPreferences::navigateToPan)
},
pref = screenModel.preferences.navigateToPan(),
)
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_dual_page_split),
checked = dualPageSplitPaged,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageSplitPaged)
},
pref = screenModel.preferences.dualPageSplitPaged(),
)
if (dualPageSplitPaged) {
val dualPageInvertPaged by screenModel.preferences.dualPageInvertPaged().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_dual_page_invert),
checked = dualPageInvertPaged,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageInvertPaged)
},
pref = screenModel.preferences.dualPageInvertPaged(),
)
}
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_page_rotate),
checked = dualPageRotateToFit,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFit)
},
pref = screenModel.preferences.dualPageRotateToFit(),
)
if (dualPageRotateToFit) {
val dualPageRotateToFitInvert by screenModel.preferences.dualPageRotateToFitInvert().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_page_rotate_invert),
checked = dualPageRotateToFitInvert,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFitInvert)
},
pref = screenModel.preferences.dualPageRotateToFitInvert(),
)
}
}
@ -172,25 +147,14 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
HeadingItem(R.string.webtoon_viewer)
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
SelectItem(
label = stringResource(R.string.pref_viewer_nav),
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
selectedIndex = navigationModeWebtoon,
onSelect = { screenModel.preferences.navigationModeWebtoon().set(it) },
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
TapZonesItems(
selected = navigationModeWebtoon,
onSelect = screenModel.preferences.navigationModeWebtoon()::set,
invertMode = webtoonNavInverted,
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set,
)
if (navigationModeWebtoon != 5) {
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
SelectItem(
label = stringResource(R.string.pref_read_with_tapping_inverted),
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == webtoonNavInverted },
onSelect = {
screenModel.preferences.webtoonNavInverted().set(tappingInvertModeOptions[it].second)
},
)
}
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
SliderItem(
label = stringResource(R.string.pref_webtoon_side_padding),
@ -203,54 +167,63 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
},
)
val cropBordersWebtoon by screenModel.preferences.cropBordersWebtoon().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_crop_borders),
checked = cropBordersWebtoon,
onClick = {
screenModel.togglePreference(ReaderPreferences::cropBordersWebtoon)
},
pref = screenModel.preferences.cropBordersWebtoon(),
)
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_dual_page_split),
checked = dualPageSplitWebtoon,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageSplitWebtoon)
},
pref = screenModel.preferences.dualPageSplitWebtoon(),
)
if (dualPageSplitWebtoon) {
val dualPageInvertWebtoon by screenModel.preferences.dualPageInvertWebtoon()
.collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_dual_page_invert),
checked = dualPageInvertWebtoon,
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageInvertWebtoon)
},
pref = screenModel.preferences.dualPageInvertWebtoon(),
)
}
if (!isReleaseBuildType) {
val longStripSplitWebtoon by screenModel.preferences.longStripSplitWebtoon()
.collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_long_strip_split),
checked = longStripSplitWebtoon,
onClick = {
screenModel.togglePreference(ReaderPreferences::longStripSplitWebtoon)
},
pref = screenModel.preferences.longStripSplitWebtoon(),
)
}
val webtoonDoubleTapZoomEnabled by screenModel.preferences.webtoonDoubleTapZoomEnabled().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_double_tap_zoom),
checked = webtoonDoubleTapZoomEnabled,
onClick = {
screenModel.togglePreference(ReaderPreferences::webtoonDoubleTapZoomEnabled)
},
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
)
}
@Composable
private fun ColumnScope.TapZonesItems(
selected: Int,
onSelect: (Int) -> Unit,
invertMode: ReaderPreferences.TappingInvertMode,
onSelectInvertMode: (ReaderPreferences.TappingInvertMode) -> Unit,
) {
SettingsChipRow(R.string.pref_viewer_nav) {
ReaderPreferences.TapZones.mapIndexed { index, it ->
FilterChip(
selected = selected == index,
onClick = { onSelect(index) },
label = { Text(stringResource(it)) },
)
}
}
if (selected != 5) {
SettingsChipRow(R.string.pref_read_with_tapping_inverted) {
tappingInvertModeOptions.map { (stringRes, mode) ->
FilterChip(
selected = mode == invertMode,
onClick = { onSelectInvertMode(mode) },
label = { Text(stringResource(stringRes)) },
)
}
}
}
}

View File

@ -24,11 +24,13 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -50,8 +52,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.VerticalDivider
import java.text.DateFormat
private const val UnsetStatusTextAlpha = 0.5F
@ -98,7 +98,7 @@ fun TrackInfoDialogHome(
},
onChaptersClick = { onChapterClick(item) },
score = item.service.displayScore(item.track.toDbTrack())
.takeIf { supportsScoring && item.track.score != 0F },
.takeIf { supportsScoring && item.track.score != 0.0 },
onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring },
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) }
@ -211,7 +211,7 @@ private fun TrackInfoItem(
}
if (onStartDateClick != null && onEndDateClick != null) {
Divider()
HorizontalDivider()
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
TrackDetailsItem(
modifier = Modifier.weight(1F),

View File

@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DatePicker
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.SelectableDates
@ -34,7 +35,6 @@ import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.WheelNumberPicker
import tachiyomi.presentation.core.components.WheelTextPicker
import tachiyomi.presentation.core.components.material.AlertDialogContent
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart
@ -79,8 +79,8 @@ fun TrackStatusSelector(
}
}
}
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
},
onConfirm = onConfirm,
onDismissRequest = onDismissRequest,

View File

@ -33,6 +33,7 @@ import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -60,7 +61,6 @@ import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.screens.EmptyScreen
@ -143,7 +143,7 @@ fun TrackServiceSearch(
}
},
)
Divider()
HorizontalDivider()
}
},
bottomBar = {

View File

@ -28,7 +28,7 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesState
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.FastScrollLazyColumn
@ -40,10 +40,9 @@ import kotlin.time.Duration.Companion.seconds
@Composable
fun UpdateScreen(
state: UpdatesState,
state: UpdatesScreenModel.State,
snackbarHostState: SnackbarHostState,
lastUpdated: Long,
relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit,
onSelectAll: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
@ -114,7 +113,7 @@ fun UpdateScreen(
}
updatesUiItems(
uiModels = state.getUiModel(context, relativeTime),
uiModels = state.getUiModel(context),
selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover,
@ -209,7 +208,7 @@ private fun UpdatesBottomBar(
)
}
sealed class UpdatesUiModel {
data class Header(val date: String) : UpdatesUiModel()
data class Item(val item: UpdatesItem) : UpdatesUiModel()
sealed interface UpdatesUiModel {
data class Header(val date: String) : UpdatesUiModel
data class Item(val item: UpdatesItem) : UpdatesUiModel
}

View File

@ -0,0 +1,13 @@
package eu.kanade.presentation.util
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
private val formatter = DecimalFormat(
"#.###",
DecimalFormatSymbols().apply { decimalSeparator = '.' },
)
fun formatChapterNumber(chapterNumber: Double): String {
return formatter.format(chapterNumber)
}

View File

@ -4,8 +4,9 @@ import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.webkit.WebResourceRequest
import android.webkit.WebView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.LoadingState
@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator
import com.google.accompanist.web.rememberWebViewState
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getHtml
import eu.kanade.tachiyomi.util.system.setDefaultSettings
import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.material.Scaffold
@Composable
@ -46,7 +52,53 @@ fun WebViewScreenContent(
) {
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
val navigator = rememberWebViewNavigator()
val uriHandler = LocalUriHandler.current
val scope = rememberCoroutineScope()
var currentUrl by remember { mutableStateOf(url) }
var showCloudflareHelp by remember { mutableStateOf(false) }
val webClient = remember {
object : AccompanistWebViewClient() {
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
url?.let {
currentUrl = it
onUrlChange(it)
}
}
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
scope.launch {
val html = view.getHtml()
showCloudflareHelp = "window._cf_chl_opt" in html
}
}
override fun doUpdateVisitedHistory(
view: WebView,
url: String?,
isReload: Boolean,
) {
super.doUpdateVisitedHistory(view, url, isReload)
url?.let {
currentUrl = it
onUrlChange(it)
}
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?,
): Boolean {
request?.let {
view?.loadUrl(it.url.toString(), headers)
}
return super.shouldOverrideUrlLoading(view, request)
}
}
}
Scaffold(
topBar = {
@ -116,61 +168,38 @@ fun WebViewScreenContent(
}
},
) { contentPadding ->
val webClient = remember {
object : AccompanistWebViewClient() {
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
url?.let {
currentUrl = it
onUrlChange(it)
}
}
override fun doUpdateVisitedHistory(
view: WebView,
url: String?,
isReload: Boolean,
) {
super.doUpdateVisitedHistory(view, url, isReload)
url?.let {
currentUrl = it
onUrlChange(it)
}
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?,
): Boolean {
request?.let {
view?.loadUrl(it.url.toString(), headers)
}
return super.shouldOverrideUrlLoading(view, request)
}
Column(
modifier = Modifier.padding(contentPadding),
) {
if (showCloudflareHelp) {
WarningBanner(
textRes = R.string.information_cloudflare_help,
modifier = Modifier.clickable {
uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
},
)
}
WebView(
state = state,
modifier = Modifier.weight(1f),
navigator = navigator,
onCreated = { webView ->
webView.setDefaultSettings()
// Debug mode (chrome://inspect/#devices)
if (BuildConfig.DEBUG &&
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
) {
WebView.setWebContentsDebuggingEnabled(true)
}
headers["user-agent"]?.let {
webView.settings.userAgentString = it
}
},
client = webClient,
)
}
WebView(
state = state,
modifier = Modifier
.padding(contentPadding)
.fillMaxSize(),
navigator = navigator,
onCreated = { webView ->
webView.setDefaultSettings()
// Debug mode (chrome://inspect/#devices)
if (BuildConfig.DEBUG &&
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
) {
WebView.setWebContentsDebuggingEnabled(true)
}
headers["user-agent"]?.let {
webView.settings.userAgentString = it
}
},
client = webClient,
)
}
}

View File

@ -29,5 +29,5 @@ object AppInfo {
*
* @since extension-lib 1.5
*/
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.values().map { it.mime }
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.entries.map { it.mime }
}

View File

@ -5,8 +5,8 @@ import android.os.Build
import androidx.core.content.ContextCompat
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences
@ -39,11 +39,11 @@ import tachiyomi.core.provider.AndroidDownloadFolderProvider
import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.Database
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.DateColumnAdapter
import tachiyomi.data.History
import tachiyomi.data.Mangas
import tachiyomi.data.dateAdapter
import tachiyomi.data.listOfStringsAdapter
import tachiyomi.data.updateStrategyAdapter
import tachiyomi.data.StringListColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences
@ -92,11 +92,11 @@ class AppModule(val app: Application) : InjektModule {
Database(
driver = get(),
historyAdapter = History.Adapter(
last_readAdapter = dateAdapter,
last_readAdapter = DateColumnAdapter,
),
mangasAdapter = Mangas.Adapter(
genreAdapter = listOfStringsAdapter,
update_strategyAdapter = updateStrategyAdapter,
genreAdapter = StringListColumnAdapter,
update_strategyAdapter = UpdateStrategyColumnAdapter,
),
)
}

View File

@ -14,8 +14,6 @@ import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.preference.minusAssign
import eu.kanade.tachiyomi.util.preference.plusAssign
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import eu.kanade.tachiyomi.util.system.toast
@ -23,6 +21,8 @@ import eu.kanade.tachiyomi.util.system.workManager
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getEnum
import tachiyomi.core.preference.minusAssign
import tachiyomi.core.preference.plusAssign
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED

View File

@ -1,20 +1,14 @@
package eu.kanade.tachiyomi.data.backup
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
object BackupConst {
private const val NAME = "BackupRestoreServices"
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
// Filter options
internal const val BACKUP_CATEGORY = 0x1
internal const val BACKUP_CATEGORY_MASK = 0x1
internal const val BACKUP_CHAPTER = 0x2
internal const val BACKUP_CHAPTER_MASK = 0x2
internal const val BACKUP_HISTORY = 0x4
internal const val BACKUP_HISTORY_MASK = 0x4
internal const val BACKUP_TRACK = 0x8
internal const val BACKUP_TRACK_MASK = 0x8
internal const val BACKUP_ALL = 0xF
// Filter options
internal object BackupConst {
const val BACKUP_CATEGORY = 0x1
const val BACKUP_CATEGORY_MASK = 0x1
const val BACKUP_CHAPTER = 0x2
const val BACKUP_CHAPTER_MASK = 0x2
const val BACKUP_HISTORY = 0x4
const val BACKUP_HISTORY_MASK = 0x4
const val BACKUP_TRACK = 0x8
const val BACKUP_TRACK_MASK = 0x8
const val BACKUP_ALL = 0xF
}

View File

@ -30,15 +30,15 @@ import logcat.LogPriority
import okio.buffer
import okio.gzip
import okio.sink
import tachiyomi.core.util.lang.toLong
import tachiyomi.core.util.system.logcat
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Manga_sync
import tachiyomi.data.Mangas
import tachiyomi.data.updateStrategyAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.history.model.HistoryUpdate
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetFavorites
@ -60,6 +60,7 @@ class BackupManager(
private val libraryPreferences: LibraryPreferences = Injekt.get()
private val getCategories: GetCategories = Injekt.get()
private val getFavorites: GetFavorites = Injekt.get()
private val getHistory: GetHistory = Injekt.get()
internal val parser = ProtoBuf
@ -79,7 +80,7 @@ class BackupManager(
backupMangas(databaseManga, flags),
backupCategories(flags),
emptyList(),
backupExtensionInfo(databaseManga),
prepExtensionInfoForSync(databaseManga),
)
var file: UniFile? = null
@ -133,7 +134,7 @@ class BackupManager(
}
}
fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
fun prepExtensionInfoForSync(mangas: List<Manga>): List<BackupSource> {
return mangas
.asSequence()
.map(Manga::source)
@ -204,11 +205,11 @@ class BackupManager(
// Check if user wants history information in backup
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
val historyByMangaId = handler.awaitList(true) { historyQueries.getHistoryByMangaId(manga.id) }
val historyByMangaId = getHistory.await(manga.id)
if (historyByMangaId.isNotEmpty()) {
val history = historyByMangaId.map { history ->
val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapter_id) }
BackupHistory(chapter.url, history.last_read?.time ?: 0L, history.time_read)
val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapterId) }
BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration)
}
if (history.isNotEmpty()) {
mangaObject.history = history
@ -262,7 +263,7 @@ class BackupManager(
}
if (!found) {
// Let the db assign the id
val id = handler.awaitOne {
val id = handler.awaitOneExecutable {
categoriesQueries.insert(category.name, category.order, category.flags)
categoriesQueries.selectLastInsertedRowId()
}
@ -413,7 +414,7 @@ class BackupManager(
track.last_chapter_read,
track.total_chapters,
track.status,
track.score.toDouble(),
track.score,
track.remote_url,
track.start_date,
track.finish_date,
@ -486,7 +487,7 @@ class BackupManager(
* @return id of [Manga], null if not found
*/
private suspend fun insertManga(manga: Manga): Long {
return handler.awaitOne(true) {
return handler.awaitOneExecutable(true) {
mangasQueries.insert(
source = manga.source,
url = manga.url,
@ -524,17 +525,17 @@ class BackupManager(
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite.toLong(),
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = null,
calculateInterval = null,
initialized = manga.initialized.toLong(),
initialized = manga.initialized,
viewer = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
mangaId = manga.id,
updateStrategy = manga.updateStrategy.let(updateStrategyAdapter::encode),
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
)
}
return manga.id
@ -574,8 +575,8 @@ class BackupManager(
url = null,
name = null,
scanlator = null,
read = chapter.read.toLong(),
bookmark = chapter.bookmark.toLong(),
read = chapter.read,
bookmark = chapter.bookmark,
lastPageRead = chapter.lastPageRead,
chapterNumber = null,
sourceOrder = null,

View File

@ -36,7 +36,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
return try {
val restorer = BackupRestorer(context, notifier)
restorer.restoreBackup(uri, sync)
restorer.syncFromBackup(uri, sync)
Result.success()
} catch (e: Exception) {
if (e is CancellationException) {

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
@ -12,10 +13,15 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.text.SimpleDateFormat
import java.time.ZonedDateTime
import java.util.Date
import java.util.Locale
@ -23,6 +29,12 @@ class BackupRestorer(
private val context: Context,
private val notifier: BackupNotifier,
) {
private val updateManga: UpdateManga = Injekt.get()
private val chapterRepository: ChapterRepository = Injekt.get()
private val setFetchInterval: SetFetchInterval = Injekt.get()
private var now = ZonedDateTime.now()
private var currentFetchWindow = setFetchInterval.getWindow(now)
private var backupManager = BackupManager(context)
@ -36,7 +48,7 @@ class BackupRestorer(
private val errors = mutableListOf<Pair<Date, String>>()
suspend fun restoreBackup(uri: Uri, sync: Boolean): Boolean {
suspend fun syncFromBackup(uri: Uri, sync: Boolean): Boolean {
val startTime = System.currentTimeMillis()
restoreProgress = 0
errors.clear()
@ -51,7 +63,7 @@ class BackupRestorer(
val logFile = writeErrorLog()
if (sync) {
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name, contentTitle = context.getString(R.string.sync_complete))
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name, contentTitle = context.getString(R.string.library_sync_complete))
} else {
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
}
@ -90,6 +102,8 @@ class BackupRestorer(
// Store source mapping for error messages
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name }
now = ZonedDateTime.now()
currentFetchWindow = setFetchInterval.getWindow(now)
return coroutineScope {
// Restore individual manga
@ -122,7 +136,7 @@ class BackupRestorer(
try {
val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
if (dbManga == null) {
val restoredManga = if (dbManga == null) {
// Manga not in database
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
} else {
@ -132,6 +146,7 @@ class BackupRestorer(
// Fetch rest of manga information
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
}
updateManga.awaitUpdateFetchInterval(restoredManga, now, currentFetchWindow)
} catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
@ -139,7 +154,7 @@ class BackupRestorer(
restoreProgress += 1
if (sync) {
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.syncing_data))
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.syncing_library))
} else {
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.restoring_backup))
}
@ -159,10 +174,11 @@ class BackupRestorer(
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
) {
): Manga {
val fetchedManga = backupManager.restoreNewManga(manga)
backupManager.restoreChapters(fetchedManga, chapters)
restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
return fetchedManga
}
private suspend fun restoreNewManga(
@ -172,9 +188,10 @@ class BackupRestorer(
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
) {
): Manga {
backupManager.restoreChapters(backupManga, chapters)
restoreExtras(backupManga, categories, history, tracks, backupCategories)
return backupManga
}
private suspend fun restoreExtras(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {

View File

@ -26,7 +26,7 @@ data class BackupChapter(
return Chapter.create().copy(
url = this@BackupChapter.url,
name = this@BackupChapter.name,
chapterNumber = this@BackupChapter.chapterNumber,
chapterNumber = this@BackupChapter.chapterNumber.toDouble(),
scanlator = this@BackupChapter.scanlator,
read = this@BackupChapter.read,
bookmark = this@BackupChapter.bookmark,
@ -39,11 +39,11 @@ data class BackupChapter(
}
}
val backupChapterMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, read: Boolean, bookmark: Boolean, lastPageRead: Long, chapterNumber: Float, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long ->
val backupChapterMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, read: Boolean, bookmark: Boolean, lastPageRead: Long, chapterNumber: Double, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long ->
BackupChapter(
url = url,
name = name,
chapterNumber = chapterNumber,
chapterNumber = chapterNumber.toFloat(),
scanlator = scanlator,
read = read,
bookmark = bookmark,

View File

@ -44,7 +44,7 @@ data class BackupTracking(
title = this@BackupTracking.title,
lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(),
totalChapters = this@BackupTracking.totalChapters.toLong(),
score = this@BackupTracking.score,
score = this@BackupTracking.score.toDouble(),
status = this@BackupTracking.status.toLong(),
startDate = this@BackupTracking.startedReadingDate,
finishDate = this@BackupTracking.finishedReadingDate,
@ -54,7 +54,7 @@ data class BackupTracking(
}
val backupTrackMapper = {
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Float, remoteUrl: String, startDate: Long, finishDate: Long ->
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long ->
BackupTracking(
syncId = syncId.toInt(),
mediaId = mediaId,
@ -63,7 +63,7 @@ val backupTrackMapper = {
title = title,
lastChapterRead = lastChapterRead.toFloat(),
totalChapters = totalChapters.toInt(),
score = score,
score = score.toFloat(),
status = status.toInt(),
startedReadingDate = startDate,
finishedReadingDate = finishDate,

View File

@ -6,7 +6,6 @@ import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Response

View File

@ -36,7 +36,7 @@ fun Chapter.toDomainChapter(): DomainChapter? {
url = url,
name = name,
dateUpload = date_upload,
chapterNumber = chapter_number,
chapterNumber = chapter_number.toDouble(),
scanlator = scanlator,
lastModifiedAt = last_modified,
)

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.model.Chapter

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.interactor.GetChapter

View File

@ -66,8 +66,10 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_UNREAD
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.model.SourceNotInstalledException
@ -77,6 +79,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.time.ZonedDateTime
import java.util.Date
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
@ -101,6 +104,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private val getTracks: GetTracks = Injekt.get()
private val insertTrack: InsertTrack = Injekt.get()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
private val setFetchInterval: SetFetchInterval = Injekt.get()
private val notifier = LibraryUpdateNotifier(context)
@ -227,6 +231,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val hasDownloads = AtomicBoolean(false)
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
val fetchWindow by lazy { setFetchInterval.getWindow(ZonedDateTime.now()) }
coroutineScope {
mangaToUpdate.groupBy { it.manga.source }.values
.map { mangaInSource ->
@ -247,6 +253,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
manga,
) {
when {
manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_completed))
@ -256,12 +265,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && manga.nextUpdate !in fetchWindow.first.rangeTo(fetchWindow.second) ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
else -> {
try {
val newChapters = updateManga(manga)
val newChapters = updateManga(manga, fetchWindow)
.sortedByDescending { it.sourceOrder }
if (newChapters.isNotEmpty()) {
@ -317,6 +326,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
)
}
if (skippedUpdates.isNotEmpty()) {
// TODO: surface skipped reasons to user
logcat {
skippedUpdates
.groupBy { it.second }
.map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
.joinToString()
}
notifier.showUpdateSkippedNotification(skippedUpdates.size)
}
}
@ -333,7 +349,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
* @param manga the manga to update.
* @return a pair of the inserted and removed chapters.
*/
private suspend fun updateManga(manga: Manga): List<Chapter> {
private suspend fun updateManga(manga: Manga, fetchWindow: Pair<Long, Long>): List<Chapter> {
val source = sourceManager.getOrStub(manga.source)
// Update manga metadata if needed
@ -348,7 +364,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// to get latest data so it doesn't get overwritten later on
val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList()
return syncChaptersWithSource.await(chapters, dbManga, source)
return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
}
private suspend fun updateCovers() {

View File

@ -6,13 +6,13 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import coil.imageLoader
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.download.Downloader
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.Constants
@ -29,8 +30,6 @@ import tachiyomi.core.util.lang.launchUI
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
class LibraryUpdateNotifier(private val context: Context) {
@ -275,20 +274,14 @@ class LibraryUpdateNotifier(private val context: Context) {
.size(NOTIF_ICON_SIZE)
.build()
val drawable = context.imageLoader.execute(request).drawable
return (drawable as? BitmapDrawable)?.bitmap
return drawable?.getBitmapOrNull()
}
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
val formatter = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' },
)
val displayableChapterNumbers = chapters
.filter { it.isRecognizedNumber }
.sortedBy { it.chapterNumber }
.map { formatter.format(it.chapterNumber) }
.map { formatChapterNumber(it.chapterNumber) }
.toSet()
return when (displayableChapterNumbers.size) {

View File

@ -152,8 +152,8 @@ sealed class Image(
}
}
sealed class Location {
data class Pictures private constructor(val relativePath: String) : Location() {
sealed interface Location {
data class Pictures private constructor(val relativePath: String) : Location {
companion object {
fun create(relativePath: String = ""): Pictures {
return Pictures(relativePath)
@ -161,7 +161,7 @@ sealed class Location {
}
}
object Cache : Location()
data object Cache : Location
fun directory(context: Context): File {
return when (this) {

View File

@ -13,6 +13,7 @@ import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.toast
import logcat.LogPriority
import okhttp3.OkHttpClient
@ -20,10 +21,12 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.time.ZoneOffset
import tachiyomi.domain.track.model.Track as DomainTrack
abstract class TrackService(val id: Long) {
@ -62,7 +65,7 @@ abstract class TrackService(val id: Long) {
abstract fun getScoreList(): List<String>
// TODO: Store all scores as 10 point in the future maybe?
open fun get10PointScore(track: DomainTrack): Float {
open fun get10PointScore(track: DomainTrack): Double {
return track.score
}
@ -107,7 +110,7 @@ abstract class TrackService(val id: Long) {
val hasReadChapters = allChapters.any { it.read }
bind(item, hasReadChapters)
val track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
Injekt.get<InsertTrack>().await(track)
@ -120,10 +123,25 @@ abstract class TrackService(val id: Long) {
?.chapterNumber?.toDouble() ?: -1.0
if (latestLocalReadChapterNumber > track.lastChapterRead) {
val updatedTrack = track.copy(
track = track.copy(
lastChapterRead = latestLocalReadChapterNumber,
)
setRemoteLastChapterRead(updatedTrack.toDbTrack(), latestLocalReadChapterNumber.toInt())
setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
}
if (track.startDate <= 0) {
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
.sortedBy { it.readAt }
.firstOrNull()
?.readAt
firstReadChapterDate?.let {
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
track = track.copy(
startDate = startDate,
)
setRemoteStartDate(track.toDbTrack(), startDate)
}
}
}
@ -145,7 +163,7 @@ abstract class TrackService(val id: Long) {
}
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
if (track.last_chapter_read == 0F && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
track.status = getReadingStatus()
}
track.last_chapter_read = chapterNumber.toFloat()

View File

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
@ -94,9 +93,9 @@ class Anilist(id: Long) : TrackService(id), DeletableTrackService {
}
}
override fun get10PointScore(track: DomainTrack): Float {
override fun get10PointScore(track: DomainTrack): Double {
// Score is stored in 100 point format
return track.score / 10f
return track.score / 10.0
}
override fun indexToScore(index: Int): Float {

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy

View File

@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.bangumi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Interceptor
@ -67,7 +66,7 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
val newFormBody = FormBody.Builder()
for (i in 0 until oidFormBody.size) {
for (i in 0..<oidFormBody.size) {
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
}
newFormBody.add("access_token", token)

View File

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kitsu
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.Response

View File

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy

View File

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.shikimori
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.Response

View File

@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.preference.plusAssign
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
@ -19,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow
import logcat.LogPriority
import tachiyomi.core.preference.plusAssign
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
@ -238,10 +238,10 @@ class ExtensionManager(
/**
* Uninstalls the extension that matches the given package name.
*
* @param pkgName The package name of the application to uninstall.
* @param extension The extension to uninstall.
*/
fun uninstallExtension(pkgName: String) {
installer.uninstallApk(pkgName)
fun uninstallExtension(extension: Extension) {
installer.uninstallApk(extension.pkgName)
}
/**
@ -260,18 +260,13 @@ class ExtensionManager(
val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
_untrustedExtensionsFlow.value -= nowTrustedExtensions
val ctx = context
launchNow {
nowTrustedExtensions
.map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
}
.map { it.await() }
.forEach { result ->
if (result is LoadResult.Success) {
registerNewExtension(result.extension)
}
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
}
}

View File

@ -125,7 +125,7 @@ internal class ExtensionGithubApi {
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.map(extensionSourceMapper).orEmpty(),
apkName = it.apk,
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}",
iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png",
)
}
}

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.extension.model
sealed class LoadResult {
class Success(val extension: Extension.Installed) : LoadResult()
class Untrusted(val extension: Extension.Untrusted) : LoadResult()
object Error : LoadResult()
sealed interface LoadResult {
data class Success(val extension: Extension.Installed) : LoadResult
data class Untrusted(val extension: Extension.Untrusted) : LoadResult
data object Error : LoadResult
}

View File

@ -6,6 +6,6 @@ fun SChapter.copyFrom(other: Chapters) {
name = other.name
url = other.url
date_upload = other.date_upload
chapter_number = other.chapter_number
chapter_number = other.chapter_number.toFloat()
scanlator = other.scanlator
}

View File

@ -54,20 +54,20 @@ class ExtensionFilterScreenModel(
}
}
sealed class ExtensionFilterEvent {
object FailedFetchingLanguages : ExtensionFilterEvent()
sealed interface ExtensionFilterEvent {
data object FailedFetchingLanguages : ExtensionFilterEvent
}
sealed class ExtensionFilterState {
sealed interface ExtensionFilterState {
@Immutable
object Loading : ExtensionFilterState()
data object Loading : ExtensionFilterState
@Immutable
data class Success(
val languages: List<String>,
val enabledLanguages: Set<String> = emptySet(),
) : ExtensionFilterState() {
) : ExtensionFilterState {
val isEmpty: Boolean
get() = languages.isEmpty()

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