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")) initWith(getByName("release"))
buildConfigField("boolean", "PREVIEW", "true") buildConfigField("boolean", "PREVIEW", "true")
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks.add("release")
val debugType = getByName("debug") val debugType = getByName("debug")
signingConfig = debugType.signingConfig
versionNameSuffix = debugType.versionNameSuffix versionNameSuffix = debugType.versionNameSuffix
applicationIdSuffix = debugType.applicationIdSuffix applicationIdSuffix = debugType.applicationIdSuffix
matchingFallbacks.add("release")
} }
create("benchmark") { create("benchmark") {
initWith(getByName("release")) 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 { 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.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.release.interactor.GetApplicationRelease import tachiyomi.domain.release.interactor.GetApplicationRelease
@ -100,10 +101,11 @@ class DomainModule : InjektModule {
addFactory { GetNextChapters(get(), get(), get()) } addFactory { GetNextChapters(get(), get(), get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetFetchInterval(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) } addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) } addFactory { SetMangaViewerFlags(get()) }
addFactory { NetworkToLocalManga(get()) } addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get()) } addFactory { UpdateManga(get(), get()) }
addFactory { SetMangaCategories(get()) } addFactory { SetMangaCategories(get()) }
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) } addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }

View File

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

View File

@ -72,9 +72,9 @@ class SetReadStatus(
suspend fun await(manga: Manga, read: Boolean) = suspend fun await(manga: Manga, read: Boolean) =
await(manga.id, read) await(manga.id, read)
sealed class Result { sealed interface Result {
object Success : Result() data object Success : Result
object NoChapters : Result() data object NoChapters : Result
data class InternalError(val error: Throwable) : 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.lang.Long.max import java.lang.Long.max
import java.time.ZonedDateTime
import java.util.Date import java.util.Date
import java.util.TreeSet import java.util.TreeSet
@ -48,11 +49,15 @@ class SyncChaptersWithSource(
rawSourceChapters: List<SChapter>, rawSourceChapters: List<SChapter>,
manga: Manga, manga: Manga,
source: Source, source: Source,
manualFetch: Boolean = false,
fetchWindow: Pair<Long, Long> = Pair(0, 0),
): List<Chapter> { ): List<Chapter> {
if (rawSourceChapters.isEmpty() && !source.isLocal()) { if (rawSourceChapters.isEmpty() && !source.isLocal()) {
throw NoChaptersException() throw NoChaptersException()
} }
val now = ZonedDateTime.now()
val sourceChapters = rawSourceChapters val sourceChapters = rawSourceChapters
.distinctBy { it.url } .distinctBy { it.url }
.mapIndexed { i, sChapter -> .mapIndexed { i, sChapter ->
@ -134,14 +139,21 @@ class SyncChaptersWithSource(
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions. // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
updateManga.awaitUpdateFetchInterval(
manga,
now,
fetchWindow,
)
}
return emptyList() return emptyList()
} }
val reAdded = mutableListOf<Chapter>() val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Float>() val deletedChapterNumbers = TreeSet<Double>()
val deletedReadChapterNumbers = TreeSet<Float>() val deletedReadChapterNumbers = TreeSet<Double>()
val deletedBookmarkedChapterNumbers = TreeSet<Float>() val deletedBookmarkedChapterNumbers = TreeSet<Double>()
toDelete.forEach { chapter -> toDelete.forEach { chapter ->
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber) if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
@ -188,6 +200,7 @@ class SyncChaptersWithSource(
val chapterUpdates = toChange.map { it.toChapterUpdate() } val chapterUpdates = toChange.map { it.toChapterUpdate() }
updateChapter.awaitAll(chapterUpdates) updateChapter.awaitAll(chapterUpdates)
} }
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
// Set this manga as updated since chapters were changed // Set this manga as updated since chapters were changed
// Note that last_update actually represents last time the chapter list changed at all // 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.url = url
it.name = name it.name = name
it.date_upload = dateUpload it.date_upload = dateUpload
it.chapter_number = chapterNumber it.chapter_number = chapterNumber.toFloat()
it.scanlator = scanlator it.scanlator = scanlator
} }
} }
@ -22,7 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
name = sChapter.name, name = sChapter.name,
url = sChapter.url, url = sChapter.url,
dateUpload = sChapter.date_upload, dateUpload = sChapter.date_upload,
chapterNumber = sChapter.chapter_number, chapterNumber = sChapter.chapter_number.toDouble(),
scanlator = sChapter.scanlator?.ifBlank { null }, scanlator = sChapter.scanlator?.ifBlank { null },
) )
} }
@ -49,7 +49,7 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
it.last_page_read = lastPageRead.toInt() it.last_page_read = lastPageRead.toInt()
it.date_fetch = dateFetch it.date_fetch = dateFetch
it.date_upload = dateUpload it.date_upload = dateUpload
it.chapter_number = chapterNumber it.chapter_number = chapterNumber.toFloat()
it.source_order = sourceOrder.toInt() it.source_order = sourceOrder.toInt()
it.last_modified = lastModifiedAt 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.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.interactor.getCurrentFetchRange
import tachiyomi.domain.manga.interactor.updateIntervalMeta
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
@ -17,6 +15,7 @@ import java.util.Date
class UpdateManga( class UpdateManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
private val setFetchInterval: SetFetchInterval,
) { ) {
suspend fun await(mangaUpdate: MangaUpdate): Boolean { suspend fun await(mangaUpdate: MangaUpdate): Boolean {
@ -77,19 +76,14 @@ class UpdateManga(
) )
} }
suspend fun awaitUpdateIntervalMeta( suspend fun awaitUpdateFetchInterval(
manga: Manga, manga: Manga,
chapters: List<Chapter>, dateTime: ZonedDateTime = ZonedDateTime.now(),
zonedDateTime: ZonedDateTime = ZonedDateTime.now(), window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
): Boolean { ): Boolean {
val newMeta = updateIntervalMeta(manga, chapters, zonedDateTime, setCurrentFetchRange) return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
?.let { mangaRepository.update(it) }
return if (newMeta != null) { ?: false
mangaRepository.update(newMeta)
} else {
true
}
} }
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean { 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), title = ComicInfo.Title(chapter.name),
series = ComicInfo.Series(manga.title), series = ComicInfo.Series(manga.title),
number = chapter.chapterNumber.takeIf { it >= 0 }?.let { number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
if ((it.rem(1) == 0.0F)) { if ((it.rem(1) == 0.0)) {
ComicInfo.Number(it.toInt().toString()) ComicInfo.Number(it.toInt().toString())
} else { } else {
ComicInfo.Number(it.toString()) 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.last_chapter_read = lastChapterRead.toFloat()
it.total_chapters = totalChapters.toInt() it.total_chapters = totalChapters.toInt()
it.status = status.toInt() it.status = status.toInt()
it.score = score it.score = score.toFloat()
it.tracking_url = remoteUrl it.tracking_url = remoteUrl
it.started_reading_date = startDate it.started_reading_date = startDate
it.finished_reading_date = finishDate it.finished_reading_date = finishDate
@ -40,7 +40,7 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
lastChapterRead = last_chapter_read.toDouble(), lastChapterRead = last_chapter_read.toDouble(),
totalChapters = total_chapters.toLong(), totalChapters = total_chapters.toLong(),
status = status.toLong(), status = status.toLong(),
score = score, score = score.toDouble(),
remoteUrl = tracking_url, remoteUrl = tracking_url,
startDate = started_reading_date, startDate = started_reading_date,
finishDate = finished_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.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -30,6 +31,7 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.R
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource 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 eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.ScrollbarLazyColumn 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.Scaffold
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
@ -65,7 +65,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
@Composable @Composable
fun ExtensionDetailsScreen( fun ExtensionDetailsScreen(
navigateUp: () -> Unit, navigateUp: () -> Unit,
state: ExtensionDetailsState, state: ExtensionDetailsScreenModel.State,
onClickSourcePreferences: (sourceId: Long) -> Unit, onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickWhatsNew: () -> Unit, onClickWhatsNew: () -> Unit,
onClickReadme: () -> Unit, onClickReadme: () -> Unit,
@ -314,7 +314,7 @@ private fun DetailsHeader(
} }
} }
Divider() HorizontalDivider()
} }
} }
@ -356,11 +356,8 @@ private fun InfoText(
@Composable @Composable
private fun InfoDivider() { private fun InfoDivider() {
Divider( VerticalDivider(
modifier = Modifier modifier = Modifier.height(20.dp),
.height(20.dp)
.width(1.dp),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
) )
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.R
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
@ -53,7 +53,7 @@ private fun ExtensionFilterContent(
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
FastScrollLazyColumn( LazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
items(state.languages) { language -> 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.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel 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 eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.PullRefresh
@ -57,7 +57,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
fun ExtensionScreen( fun ExtensionScreen(
state: ExtensionsState, state: ExtensionsScreenModel.State,
contentPadding: PaddingValues, contentPadding: PaddingValues,
searchQuery: String?, searchQuery: String?,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
@ -108,7 +108,7 @@ fun ExtensionScreen(
@Composable @Composable
private fun ExtensionContent( private fun ExtensionContent(
state: ExtensionsState, state: ExtensionsScreenModel.State,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,

View File

@ -1,49 +1,25 @@
package eu.kanade.presentation.browse 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.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.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.Composable
import androidx.compose.runtime.State 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.GlobalSearchCardRow
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
import eu.kanade.presentation.browse.components.GlobalSearchResultItem import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource 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.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga 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.Scaffold
import tachiyomi.presentation.core.components.material.VerticalDivider
import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
fun GlobalSearchScreen( fun GlobalSearchScreen(
state: GlobalSearchScreenModel.State, state: SearchScreenModel.State,
items: Map<CatalogueSource, SearchItemResult>,
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
@ -56,80 +32,23 @@ fun GlobalSearchScreen(
) { ) {
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { GlobalSearchToolbar(
GlobalSearchToolbar( searchQuery = state.searchQuery,
searchQuery = state.searchQuery, progress = state.progress,
progress = state.progress, total = state.total,
total = state.total, navigateUp = navigateUp,
navigateUp = navigateUp, onChangeSearchQuery = onChangeSearchQuery,
onChangeSearchQuery = onChangeSearchQuery, onSearch = onSearch,
onSearch = onSearch, sourceFilter = state.sourceFilter,
scrollBehavior = scrollBehavior, onChangeSearchFilter = onChangeSearchFilter,
) onlyShowHasResults = state.onlyShowHasResults,
onToggleResults = onToggleResults,
Row( scrollBehavior = scrollBehavior,
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()
}
}, },
) { paddingValues -> ) { paddingValues ->
GlobalSearchContent( GlobalSearchContent(
items = items, items = state.filteredItems,
contentPadding = paddingValues, contentPadding = paddingValues,
getManga = getManga, getManga = getManga,
onClickSource = onClickSource, onClickSource = onClickSource,
@ -140,7 +59,8 @@ fun GlobalSearchScreen(
} }
@Composable @Composable
private fun GlobalSearchContent( internal fun GlobalSearchContent(
fromSourceId: Long? = null,
items: Map<CatalogueSource, SearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
getManga: @Composable (Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
@ -154,7 +74,7 @@ private fun GlobalSearchContent(
items.forEach { (source, result) -> items.forEach { (source, result) ->
item(key = source.id) { item(key = source.id) {
GlobalSearchResultItem( GlobalSearchResultItem(
title = source.name, title = fromSourceId?.let { "${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
subtitle = LocaleHelper.getDisplayName(source.lang), subtitle = LocaleHelper.getDisplayName(source.lang),
onClick = { onClickSource(source) }, onClick = { onClickSource(source) },
) { ) {
@ -163,18 +83,6 @@ private fun GlobalSearchContent(
GlobalSearchLoadingResultItem() GlobalSearchLoadingResultItem()
} }
is SearchItemResult.Success -> { 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( GlobalSearchCardRow(
titles = result.result, titles = result.result,
getManga = getManga, getManga = getManga,

View File

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

View File

@ -1,29 +1,24 @@
package eu.kanade.presentation.browse 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.Composable
import androidx.compose.runtime.State 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.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@Composable @Composable
fun MigrateSearchScreen( fun MigrateSearchScreen(
state: SearchScreenModel.State,
fromSourceId: Long?,
navigateUp: () -> Unit, navigateUp: () -> Unit,
state: MigrateSearchState,
getManga: @Composable (Manga) -> State<Manga>,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
onChangeSearchFilter: (SourceFilter) -> Unit,
onToggleResults: () -> Unit,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit,
@ -37,13 +32,17 @@ fun MigrateSearchScreen(
navigateUp = navigateUp, navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery, onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch, onSearch = onSearch,
sourceFilter = state.sourceFilter,
onChangeSearchFilter = onChangeSearchFilter,
onlyShowHasResults = state.onlyShowHasResults,
onToggleResults = onToggleResults,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
}, },
) { paddingValues -> ) { paddingValues ->
MigrateSearchContent( GlobalSearchContent(
sourceId = state.manga?.source ?: -1, fromSourceId = fromSourceId,
items = state.items, items = state.filteredItems,
contentPadding = paddingValues, contentPadding = paddingValues,
getManga = getManga, getManga = getManga,
onClickSource = onClickSource, 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.BaseSourceItem
import eu.kanade.presentation.browse.components.SourceIcon import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.tachiyomi.R 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 eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.source.model.Source import tachiyomi.domain.source.model.Source
import tachiyomi.presentation.core.components.Badge import tachiyomi.presentation.core.components.Badge
@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
fun MigrateSourceScreen( fun MigrateSourceScreen(
state: MigrateSourceState, state: MigrateSourceScreenModel.State,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickItem: (Source) -> Unit, onClickItem: (Source) -> Unit,
onToggleSortingDirection: () -> 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.components.AppBar
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterState import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.source.model.Source import tachiyomi.domain.source.model.Source
import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.FastScrollLazyColumn
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
@Composable @Composable
fun SourcesFilterScreen( fun SourcesFilterScreen(
navigateUp: () -> Unit, navigateUp: () -> Unit,
state: SourcesFilterState.Success, state: SourcesFilterScreenModel.State.Success,
onClickLanguage: (String) -> Unit, onClickLanguage: (String) -> Unit,
onClickSource: (Source) -> Unit, onClickSource: (Source) -> Unit,
) { ) {
@ -54,7 +54,7 @@ fun SourcesFilterScreen(
@Composable @Composable
private fun SourcesFilterContent( private fun SourcesFilterContent(
contentPadding: PaddingValues, contentPadding: PaddingValues,
state: SourcesFilterState.Success, state: SourcesFilterScreenModel.State.Success,
onClickLanguage: (String) -> Unit, onClickLanguage: (String) -> Unit,
onClickSource: (Source) -> Unit, onClickSource: (Source) -> Unit,
) { ) {

View File

@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.tachiyomi.R 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.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.source.model.Pin import tachiyomi.domain.source.model.Pin
@ -40,7 +40,7 @@ import tachiyomi.source.local.isLocal
@Composable @Composable
fun SourcesScreen( fun SourcesScreen(
state: SourcesState, state: SourcesScreenModel.State,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickItem: (Source, Listing) -> Unit, onClickItem: (Source, Listing) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
@ -192,7 +192,7 @@ fun SourceOptionsDialog(
) )
} }
sealed class SourceUiModel { sealed interface SourceUiModel {
data class Item(val source: Source) : SourceUiModel() data class Item(val source: Source) : SourceUiModel
data class Header(val language: String) : 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> { sealed class Result<out T> {
object Loading : Result<Nothing>() data object Loading : Result<Nothing>()
object Error : Result<Nothing>() data object Error : Result<Nothing>()
data class Success<out T>(val value: T) : Result<T>() 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.library.components.CommonMangaItemDefaults import eu.kanade.presentation.library.components.CommonMangaItemDefaults
import eu.kanade.presentation.library.components.MangaComfortableGridItem import eu.kanade.presentation.library.components.MangaComfortableGridItem
import eu.kanade.tachiyomi.R
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.manga.model.asMangaCover import tachiyomi.domain.manga.model.asMangaCover
@ -26,13 +30,18 @@ fun GlobalSearchCardRow(
onClick: (Manga) -> Unit, onClick: (Manga) -> Unit,
onLongClick: (Manga) -> Unit, onLongClick: (Manga) -> Unit,
) { ) {
if (titles.isEmpty()) {
EmptyResultItem()
return
}
LazyRow( LazyRow(
contentPadding = PaddingValues(MaterialTheme.padding.small), contentPadding = PaddingValues(MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
) { ) {
items(titles) { items(titles) {
val title by getManga(it) val title by getManga(it)
GlobalSearchCard( MangaItem(
title = title.title, title = title.title,
cover = title.asMangaCover(), cover = title.asMangaCover(),
isFavorite = title.favorite, isFavorite = title.favorite,
@ -44,7 +53,7 @@ fun GlobalSearchCardRow(
} }
@Composable @Composable
private fun GlobalSearchCard( private fun MangaItem(
title: String, title: String,
cover: MangaCover, cover: MangaCover,
isFavorite: Boolean, isFavorite: Boolean,
@ -54,6 +63,7 @@ private fun GlobalSearchCard(
Box(modifier = Modifier.width(96.dp)) { Box(modifier = Modifier.width(96.dp)) {
MangaComfortableGridItem( MangaComfortableGridItem(
title = title, title = title,
titleMaxLines = 3,
coverData = cover, coverData = cover,
coverBadgeStart = { coverBadgeStart = {
InLibraryBadge(enabled = isFavorite) 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 @Composable
fun GlobalSearchLoadingResultItem() { fun GlobalSearchLoadingResultItem() {
Box( Box(

View File

@ -1,13 +1,36 @@
package eu.kanade.presentation.browse.components 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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.SearchToolbar 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 @Composable
fun GlobalSearchToolbar( fun GlobalSearchToolbar(
@ -17,24 +40,89 @@ fun GlobalSearchToolbar(
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
sourceFilter: SourceFilter,
onChangeSearchFilter: (SourceFilter) -> Unit,
onlyShowHasResults: Boolean,
onToggleResults: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
) { ) {
Box { Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
SearchToolbar( Box {
searchQuery = searchQuery, SearchToolbar(
onChangeSearchQuery = onChangeSearchQuery, searchQuery = searchQuery,
onSearch = onSearch, onChangeSearchQuery = onChangeSearchQuery,
onClickCloseSearch = navigateUp, onSearch = onSearch,
navigateUp = navigateUp, onClickCloseSearch = navigateUp,
scrollBehavior = scrollBehavior, navigateUp = navigateUp,
) scrollBehavior = scrollBehavior,
if (progress in 1 until total) { )
LinearProgressIndicator( if (progress in 1..<total) {
progress = progress / total.toFloat(), LinearProgressIndicator(
modifier = Modifier progress = progress / total.toFloat(),
.align(Alignment.BottomStart) modifier = Modifier
.fillMaxWidth(), .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.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable 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.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
@ -70,6 +71,7 @@ fun NavigatorAdaptiveSheet(
*/ */
@Composable @Composable
fun AdaptiveSheet( fun AdaptiveSheet(
modifier: Modifier = Modifier,
tonalElevation: Dp = 1.dp, tonalElevation: Dp = 1.dp,
enableSwipeDismiss: Boolean = true, enableSwipeDismiss: Boolean = true,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
@ -82,6 +84,7 @@ fun AdaptiveSheet(
properties = dialogProperties, properties = dialogProperties,
) { ) {
AdaptiveSheetImpl( AdaptiveSheetImpl(
modifier = modifier,
isTabletUi = isTabletUi, isTabletUi = isTabletUi,
tonalElevation = tonalElevation, tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss, enableSwipeDismiss = enableSwipeDismiss,

View File

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

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -30,7 +31,6 @@ import androidx.compose.ui.util.fastForEachIndexed
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.HorizontalPager import tachiyomi.presentation.core.components.HorizontalPager
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.TabIndicator import tachiyomi.presentation.core.components.material.TabIndicator
object TabbedDialogPaddings { object TabbedDialogPaddings {
@ -40,6 +40,7 @@ object TabbedDialogPaddings {
@Composable @Composable
fun TabbedDialog( fun TabbedDialog(
modifier: Modifier = Modifier,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
tabTitles: List<String>, tabTitles: List<String>,
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null, tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
@ -47,6 +48,7 @@ fun TabbedDialog(
content: @Composable (Int) -> Unit, content: @Composable (Int) -> Unit,
) { ) {
AdaptiveSheet( AdaptiveSheet(
modifier = modifier,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -80,7 +82,7 @@ fun TabbedDialog(
tabOverflowMenuContent?.let { MoreMenu(it) } tabOverflowMenuContent?.let { MoreMenu(it) }
} }
Divider() HorizontalDivider()
HorizontalPager( HorizontalPager(
modifier = Modifier.animateContentSize(), 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.presentation.history.components.HistoryItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import eu.kanade.tachiyomi.ui.history.HistoryState
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -33,7 +32,7 @@ import java.util.Date
@Composable @Composable
fun HistoryScreen( fun HistoryScreen(
state: HistoryState, state: HistoryScreenModel.State,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit, onSearchQueryChange: (String?) -> Unit,
onClickCover: (mangaId: Long) -> Unit, onClickCover: (mangaId: Long) -> Unit,
@ -99,7 +98,6 @@ private fun HistoryScreenContent(
onClickDelete: (HistoryWithRelations) -> Unit, onClickDelete: (HistoryWithRelations) -> Unit,
preferences: UiPreferences = Injekt.get(), preferences: UiPreferences = Injekt.get(),
) { ) {
val relativeTime: Int = remember { preferences.relativeTime().get() }
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
FastScrollLazyColumn( FastScrollLazyColumn(
@ -120,7 +118,6 @@ private fun HistoryScreenContent(
RelativeDateHeader( RelativeDateHeader(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
date = item.date, date = item.date,
relativeTime = relativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
) )
} }
@ -139,7 +136,7 @@ private fun HistoryScreenContent(
} }
} }
sealed class HistoryUiModel { sealed interface HistoryUiModel {
data class Header(val date: Date) : HistoryUiModel() data class Header(val date: Date) : HistoryUiModel
data class Item(val item: HistoryWithRelations) : 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.lang.toTimestampString
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
private val HISTORY_ITEM_HEIGHT = 96.dp private val HISTORY_ITEM_HEIGHT = 96.dp
@ -68,7 +67,7 @@ fun HistoryItem(
text = if (history.chapterNumber > -1) { text = if (history.chapterNumber > -1) {
stringResource( stringResource(
R.string.recent_manga_time, R.string.recent_manga_time,
chapterFormatter.format(history.chapterNumber), formatChapterNumber(history.chapterNumber),
readAt, readAt,
) )
} else { } 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.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -15,7 +16,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
import tachiyomi.core.preference.TriState import tachiyomi.core.preference.TriState
@ -26,11 +26,11 @@ import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem 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.SliderItem
import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.ChoiceChip import tachiyomi.presentation.core.util.collectAsState
@Composable @Composable
fun LibrarySettingsDialog( fun LibrarySettingsDialog(
@ -181,12 +181,12 @@ private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
) { ) {
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState() val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
SettingsFlowRow(R.string.action_display_mode) { SettingsChipRow(R.string.action_display_mode) {
displayModes.map { (titleRes, mode) -> displayModes.map { (titleRes, mode) ->
ChoiceChip( FilterChip(
isSelected = displayMode == mode, selected = displayMode == mode,
onClick = { screenModel.setDisplayMode(mode) }, onClick = { screenModel.setDisplayMode(mode) },
content = { Text(stringResource(titleRes)) }, label = { Text(stringResource(titleRes)) },
) )
} }
} }
@ -211,59 +211,35 @@ private fun ColumnScope.DisplayPage(
} else { } else {
stringResource(R.string.label_default) stringResource(R.string.label_default)
}, },
onChange = { columnPreference.set(it) }, onChange = columnPreference::set,
) )
} }
HeadingItem(R.string.overlay_header) HeadingItem(R.string.overlay_header)
val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_download_badge), label = stringResource(R.string.action_display_download_badge),
checked = downloadBadge, pref = screenModel.libraryPreferences.downloadBadge(),
onClick = {
screenModel.togglePreference(LibraryPreferences::downloadBadge)
},
) )
val localBadge by screenModel.libraryPreferences.localBadge().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_local_badge), label = stringResource(R.string.action_display_local_badge),
checked = localBadge, pref = screenModel.libraryPreferences.localBadge(),
onClick = {
screenModel.togglePreference(LibraryPreferences::localBadge)
},
) )
val languageBadge by screenModel.libraryPreferences.languageBadge().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_language_badge), label = stringResource(R.string.action_display_language_badge),
checked = languageBadge, pref = screenModel.libraryPreferences.languageBadge(),
onClick = {
screenModel.togglePreference(LibraryPreferences::languageBadge)
},
) )
val showContinueReadingButton by screenModel.libraryPreferences.showContinueReadingButton().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_show_continue_reading_button), label = stringResource(R.string.action_display_show_continue_reading_button),
checked = showContinueReadingButton, pref = screenModel.libraryPreferences.showContinueReadingButton(),
onClick = {
screenModel.togglePreference(LibraryPreferences::showContinueReadingButton)
},
) )
HeadingItem(R.string.tabs_header) HeadingItem(R.string.tabs_header)
val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_show_tabs), label = stringResource(R.string.action_display_show_tabs),
checked = categoryTabs, pref = screenModel.libraryPreferences.categoryTabs(),
onClick = {
screenModel.togglePreference(LibraryPreferences::categoryTabs)
},
) )
val categoryNumberOfItems by screenModel.libraryPreferences.categoryNumberOfItems().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.action_display_show_number_of_items), label = stringResource(R.string.action_display_show_number_of_items),
checked = categoryNumberOfItems, pref = screenModel.libraryPreferences.categoryNumberOfItems(),
onClick = {
screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems)
},
) )
} }

View File

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

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
@ -9,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.category.visualName import eu.kanade.presentation.category.visualName
import tachiyomi.domain.category.model.Category 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.TabIndicator
import tachiyomi.presentation.core.components.material.TabText 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 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.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -8,6 +9,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@Composable @Composable
@ -18,15 +20,27 @@ fun DuplicateMangaDialog(
) { ) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
confirmButton = { confirmButton = {
Row { FlowRow(
TextButton(onClick = { horizontalArrangement = Arrangement.spacedBy(4.dp),
onDismissRequest() ) {
onOpenManga() TextButton(
},) { onClick = {
onDismissRequest()
onOpenManga()
},
) {
Text(text = stringResource(R.string.action_show_manga)) Text(text = stringResource(R.string.action_show_manga))
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel)) 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.MangaChapterListItem
import eu.kanade.presentation.manga.components.MangaInfoBox import eu.kanade.presentation.manga.components.MangaInfoBox
import eu.kanade.presentation.manga.components.MangaToolbar import eu.kanade.presentation.manga.components.MangaToolbar
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.ui.manga.MangaScreenState import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import eu.kanade.tachiyomi.ui.manga.chapterDecimalFormat
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@ -82,9 +82,9 @@ import java.util.Date
@Composable @Composable
fun MangaScreen( fun MangaScreen(
state: MangaScreenState.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, fetchInterval: Int?,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
@ -112,6 +112,7 @@ fun MangaScreen(
onShareClicked: (() -> Unit)?, onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?, onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?, onEditCategoryClicked: (() -> Unit)?,
onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
// For bottom action menu // For bottom action menu
@ -139,8 +140,8 @@ fun MangaScreen(
MangaScreenSmallImpl( MangaScreenSmallImpl(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
fetchInterval = fetchInterval,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
@ -160,6 +161,7 @@ fun MangaScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -174,10 +176,10 @@ fun MangaScreen(
MangaScreenLargeImpl( MangaScreenLargeImpl(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat, dateFormat = dateFormat,
fetchInterval = fetchInterval,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
@ -195,6 +197,7 @@ fun MangaScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -210,10 +213,10 @@ fun MangaScreen(
@Composable @Composable
private fun MangaScreenSmallImpl( private fun MangaScreenSmallImpl(
state: MangaScreenState.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -240,6 +243,7 @@ private fun MangaScreenSmallImpl(
onShareClicked: (() -> Unit)?, onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?, onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?, onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
// For bottom action menu // For bottom action menu
@ -279,9 +283,11 @@ private fun MangaScreenSmallImpl(
} }
val animatedTitleAlpha by animateFloatAsState( val animatedTitleAlpha by animateFloatAsState(
if (firstVisibleItemIndex > 0) 1f else 0f, if (firstVisibleItemIndex > 0) 1f else 0f,
label = "titleAlpha",
) )
val animatedBgAlpha by animateFloatAsState( val animatedBgAlpha by animateFloatAsState(
if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f, if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f,
label = "bgAlpha",
) )
MangaToolbar( MangaToolbar(
title = state.manga.title, title = state.manga.title,
@ -383,10 +389,13 @@ private fun MangaScreenSmallImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,
onTrackingClicked = onTrackingClicked, onTrackingClicked = onTrackingClicked,
onEditIntervalClicked = onEditIntervalClicked,
onEditCategory = onEditCategoryClicked, onEditCategory = onEditCategoryClicked,
) )
} }
@ -419,7 +428,6 @@ private fun MangaScreenSmallImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
@ -436,10 +444,10 @@ private fun MangaScreenSmallImpl(
@Composable @Composable
fun MangaScreenLargeImpl( fun MangaScreenLargeImpl(
state: MangaScreenState.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -466,6 +474,7 @@ fun MangaScreenLargeImpl(
onShareClicked: (() -> Unit)?, onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?, onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?, onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
// For bottom action menu // For bottom action menu
@ -596,10 +605,13 @@ fun MangaScreenLargeImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,
onTrackingClicked = onTrackingClicked, onTrackingClicked = onTrackingClicked,
onEditIntervalClicked = onEditIntervalClicked,
onEditCategory = onEditCategoryClicked, onEditCategory = onEditCategoryClicked,
) )
ExpandableMangaDescription( ExpandableMangaDescription(
@ -639,7 +651,6 @@ fun MangaScreenLargeImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
@ -701,7 +712,6 @@ private fun SharedMangaBottomActionMenu(
private fun LazyListScope.sharedChapterItems( private fun LazyListScope.sharedChapterItems(
manga: Manga, manga: Manga,
chapters: List<ChapterItem>, chapters: List<ChapterItem>,
dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -722,7 +732,7 @@ private fun LazyListScope.sharedChapterItems(
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) { title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
stringResource( stringResource(
R.string.display_mode_chapter, R.string.display_mode_chapter,
chapterDecimalFormat.format(chapterItem.chapter.chapterNumber.toDouble()), formatChapterNumber(chapterItem.chapter.chapterNumber),
) )
} else { } else {
chapterItem.chapter.name chapterItem.chapter.name
@ -730,11 +740,7 @@ private fun LazyListScope.sharedChapterItems(
date = chapterItem.chapter.dateUpload date = chapterItem.chapter.dateUpload
.takeIf { it > 0L } .takeIf { it > 0L }
?.let { ?.let {
Date(it).toRelativeString( Date(it).toRelativeString(context, dateFormat)
context,
dateRelativeTime,
dateFormat,
)
}, },
readProgress = chapterItem.chapter.lastPageRead readProgress = chapterItem.chapter.lastPageRead
.takeIf { !chapterItem.chapter.read && it > 0L } .takeIf { !chapterItem.chapter.read && it > 0L }

View File

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

View File

@ -1,11 +1,23 @@
package eu.kanade.presentation.manga.components 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.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.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.res.stringResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
import tachiyomi.presentation.core.components.WheelTextPicker
@Composable @Composable
fun DeleteChaptersDialog( 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.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite 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.filled.Warning
import androidx.compose.material.icons.outlined.AttachMoney import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.outlined.Block 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.MaterialTheme
import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.SuggestionChip import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider 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.components.material.padding
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import kotlin.math.absoluteValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)) private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@ -165,14 +166,18 @@ fun MangaActionRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
favorite: Boolean, favorite: Boolean,
trackingCount: Int, trackingCount: Int,
fetchInterval: Int?,
isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?, onTrackingClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onEditCategory: (() -> Unit)?, onEditCategory: (() -> Unit)?,
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) { Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
MangaActionButton( MangaActionButton(
title = if (favorite) { title = if (favorite) {
stringResource(R.string.in_library) stringResource(R.string.in_library)
@ -184,6 +189,14 @@ fun MangaActionRow(
onClick = onAddToLibraryClicked, onClick = onAddToLibraryClicked,
onLongClick = onEditCategory, 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) { if (onTrackingClicked != null) {
MangaActionButton( MangaActionButton(
title = if (trackingCount == 0) { title = if (trackingCount == 0) {
@ -652,11 +665,6 @@ private fun TagsChip(
modifier = modifier, modifier = modifier,
onClick = onClick, onClick = onClick,
label = { Text(text = text, style = MaterialTheme.typography.bodySmall) }, 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.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -12,7 +13,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.Divider
@Composable @Composable
fun LogoHeader() { fun LogoHeader() {
@ -29,6 +29,6 @@ fun LogoHeader() {
.size(64.dp), .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.QueryStats
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.SettingsBackupRestore import androidx.compose.material.icons.outlined.SettingsBackupRestore
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -31,7 +32,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.DownloadQueueState import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import tachiyomi.core.Constants import tachiyomi.core.Constants
import tachiyomi.presentation.core.components.ScrollbarLazyColumn 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.Scaffold
@Composable @Composable
@ -94,7 +94,7 @@ fun MoreScreen(
) )
} }
item { Divider() } item { HorizontalDivider() }
item { item {
val downloadQueueState = downloadQueueStateProvider() val downloadQueueState = downloadQueueStateProvider()
@ -147,7 +147,7 @@ fun MoreScreen(
) )
} }
item { Divider() } item { HorizontalDivider() }
item { item {
TextPreferenceWidget( TextPreferenceWidget(

View File

@ -13,8 +13,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.service.TrackPreferences 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.EditTextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.InfoWidget import eu.kanade.presentation.more.settings.widget.InfoWidget
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget 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.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
import eu.kanade.presentation.util.collectAsState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get 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 -> { is Preference.PreferenceItem.TrackingPreference -> {
val uName by Injekt.get<PreferenceStore>() val uName by Injekt.get<PreferenceStore>()
.getString(TrackPreferences.trackUsername(item.service.id)) .getString(TrackPreferences.trackUsername(item.service.id))
@ -183,6 +171,9 @@ internal fun PreferenceItem(
is Preference.PreferenceItem.InfoPreference -> { is Preference.PreferenceItem.InfoPreference -> {
InfoWidget(text = item.title) 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.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.more.settings.Preference.PreferenceItem import eu.kanade.presentation.more.settings.Preference.PreferenceItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -133,19 +132,6 @@ sealed class Preference {
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }, override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
) : PreferenceItem<String>() ) : 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. * A [PreferenceItem] for individual tracking service.
*/ */
@ -169,6 +155,16 @@ sealed class Preference {
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true } 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( 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.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache 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.core.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
@ -114,7 +114,7 @@ object SettingsAdvancedScreen : SearchableSettings {
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_debug_info), 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( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_invalidate_download_cache), title = stringResource(R.string.pref_invalidate_download_cache),
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary), 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( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_clear_database), 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.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference 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.R
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast 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.drop
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
@ -48,7 +49,6 @@ object SettingsAppearanceScreen : SearchableSettings {
return listOf( return listOf(
getThemeGroup(context = context, uiPreferences = uiPreferences), getThemeGroup(context = context, uiPreferences = uiPreferences),
getDisplayGroup(context = context, uiPreferences = uiPreferences), getDisplayGroup(context = context, uiPreferences = uiPreferences),
getTimestampGroup(uiPreferences = uiPreferences),
) )
} }
@ -59,8 +59,11 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() val appThemePref = uiPreferences.appTheme()
val amoledPref = uiPreferences.themeDarkAmoled() val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
LaunchedEffect(themeMode) { LaunchedEffect(themeMode) {
setAppCompatDelegateThemeMode(themeMode) setAppCompatDelegateThemeMode(themeMode)
@ -91,10 +94,17 @@ object SettingsAppearanceScreen : SearchableSettings {
) )
}, },
), ),
Preference.PreferenceItem.AppThemePreference( Preference.PreferenceItem.CustomPreference(
title = stringResource(R.string.pref_app_theme), 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( Preference.PreferenceItem.SwitchPreference(
pref = amoledPref, pref = amoledPref,
title = stringResource(R.string.pref_dark_theme_pure_black), title = stringResource(R.string.pref_dark_theme_pure_black),
@ -111,6 +121,7 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val langs = remember { getLangs(context) } val langs = remember { getLangs(context) }
var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") } var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") }
val now = remember { Date().time }
LaunchedEffect(currentLanguage) { LaunchedEffect(currentLanguage) {
val locale = if (currentLanguage.isEmpty()) { val locale = if (currentLanguage.isEmpty()) {
@ -136,31 +147,12 @@ object SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = uiPreferences.tabletUiMode(), pref = uiPreferences.tabletUiMode(),
title = stringResource(R.string.pref_tablet_ui_mode), title = stringResource(R.string.pref_tablet_ui_mode),
entries = TabletUiMode.values().associateWith { stringResource(it.titleResId) }, entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
onValueChanged = { onValueChanged = {
context.toast(R.string.requires_app_restart) context.toast(R.string.requires_app_restart)
true 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( Preference.PreferenceItem.ListPreference(
pref = uiPreferences.dateFormat(), pref = uiPreferences.dateFormat(),
title = stringResource(R.string.pref_date_format), title = stringResource(R.string.pref_date_format),
@ -173,14 +165,13 @@ object SettingsAppearanceScreen : SearchableSettings {
), ),
) )
} }
private fun getLangs(context: Context): Map<String, String> { private fun getLangs(context: Context): Map<String, String> {
val langs = mutableListOf<Pair<String, String>>() val langs = mutableListOf<Pair<String, String>>()
val parser = context.resources.getXml(R.xml.locales_config) val parser = context.resources.getXml(R.xml.locales_config)
var eventType = parser.eventType var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) { while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") { 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") { if (parser.getAttributeName(i) == "name") {
val langTag = parser.getAttributeValue(i) val langTag = parser.getAttributeValue(i)
val displayName = LocaleHelper.getDisplayName(langTag) val displayName = LocaleHelper.getDisplayName(langTag)

View File

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

View File

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

View File

@ -1,33 +1,18 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen
import androidx.annotation.StringRes 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.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMap
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import cafe.adriel.voyager.navigator.LocalNavigator 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.category.visualName
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.util.system.isDevFlavor
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories 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_COMPLETED
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.components.WheelTextPicker
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -142,13 +124,10 @@ object SettingsLibraryScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval() val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
val libraryUpdateDeviceRestrictionPref = libraryPreferences.libraryUpdateDeviceRestriction()
val libraryUpdateMangaRestrictionPref = libraryPreferences.libraryUpdateMangaRestriction()
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories() val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude() val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState() val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
val included by libraryUpdateCategoriesPref.collectAsState() val included by libraryUpdateCategoriesPref.collectAsState()
val excluded by libraryUpdateCategoriesExcludePref.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( return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_library_update), title = stringResource(R.string.pref_category_library_update),
preferenceItems = listOfNotNull( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryUpdateIntervalPref, pref = libraryUpdateIntervalPref,
title = stringResource(R.string.pref_library_update_interval), title = stringResource(R.string.pref_library_update_interval),
@ -205,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryUpdateDeviceRestrictionPref, pref = libraryPreferences.libraryUpdateDeviceRestriction(),
enabled = libraryUpdateInterval > 0, enabled = libraryUpdateInterval > 0,
title = stringResource(R.string.pref_library_update_restriction), title = stringResource(R.string.pref_library_update_restriction),
subtitle = stringResource(R.string.restrictions), subtitle = stringResource(R.string.restrictions),
@ -241,30 +205,16 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(R.string.pref_library_update_refresh_trackers), title = stringResource(R.string.pref_library_update_refresh_trackers),
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary), subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
), ),
// TODO: remove isDevFlavor checks once functionality is available
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryUpdateMangaRestrictionPref, pref = libraryPreferences.libraryUpdateMangaRestriction(),
title = stringResource(R.string.pref_library_update_manga_restriction), title = stringResource(R.string.pref_library_update_manga_restriction),
entries = buildMap { entries = mapOf(
put(MANGA_HAS_UNREAD, stringResource(R.string.pref_update_only_completely_read)) MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
put(MANGA_NON_READ, stringResource(R.string.pref_update_only_started)) MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
put(MANGA_NON_COMPLETED, stringResource(R.string.pref_update_only_non_completed)) MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
if (isDevFlavor) { MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
put(MANGA_OUTSIDE_RELEASE_PERIOD, 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( Preference.PreferenceItem.SwitchPreference(
pref = libraryPreferences.newShowUpdatesCount(), pref = libraryPreferences.newShowUpdatesCount(),
title = stringResource(R.string.pref_library_update_show_tab_badge), 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.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.NumberFormat import java.text.NumberFormat
@ -33,7 +33,7 @@ object SettingsReaderScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = readerPref.defaultReadingMode(), pref = readerPref.defaultReadingMode(),
title = stringResource(R.string.pref_viewer_type), title = stringResource(R.string.pref_viewer_type),
entries = ReadingModeType.values().drop(1) entries = ReadingModeType.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }, .associate { it.flagValue to stringResource(it.stringRes) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
@ -84,7 +84,7 @@ object SettingsReaderScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = readerPreferences.defaultOrientationType(), pref = readerPreferences.defaultOrientationType(),
title = stringResource(R.string.pref_rotation_type), title = stringResource(R.string.pref_rotation_type),
entries = OrientationType.values().drop(1) entries = OrientationType.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }, .associate { it.flagValue to stringResource(it.stringRes) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(

View File

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

View File

@ -10,11 +10,11 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -70,7 +70,7 @@ object SettingsSecurityScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(), pref = securityPreferences.secureScreen(),
title = stringResource(R.string.secure_screen), title = stringResource(R.string.secure_screen),
entries = SecurityPreferences.SecureScreenMode.values() entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleResId) }, .associateWith { stringResource(it.titleResId) },
), ),
Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)), 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.Modifier
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
@ -45,6 +46,7 @@ class OpenSourceLibraryLicenseScreen(
Text( Text(
text = name, text = name,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
}, },
navigationIcon = { navigationIcon = {

View File

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

View File

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

View File

@ -43,9 +43,11 @@ import kotlinx.coroutines.flow.stateIn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.util.plus 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 @Composable
override fun Content() { override fun Content() {

View File

@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@ -44,7 +45,6 @@ import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable 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.components.material.padding
import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.ThemePreviews
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
@ -75,7 +75,7 @@ private fun AppThemesList(
onItemClick: (AppTheme) -> Unit, onItemClick: (AppTheme) -> Unit,
) { ) {
val appThemes = remember { val appThemes = remember {
AppTheme.values() AppTheme.entries
.filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) } .filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
} }
LazyRow( LazyRow(
@ -123,7 +123,6 @@ fun AppThemePreviewItem(
selected: Boolean, selected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
val dividerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA)
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -133,7 +132,7 @@ fun AppThemePreviewItem(
color = if (selected) { color = if (selected) {
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else { } else {
dividerColor DividerDefaults.color
}, },
shape = RoundedCornerShape(17.dp), shape = RoundedCornerShape(17.dp),
) )
@ -180,7 +179,7 @@ fun AppThemePreviewItem(
modifier = Modifier modifier = Modifier
.padding(start = 8.dp, top = 2.dp) .padding(start = 8.dp, top = 2.dp)
.background( .background(
color = dividerColor, color = DividerDefaults.color,
shape = MaterialTheme.shapes.small, shape = MaterialTheme.shapes.small,
) )
.fillMaxWidth(0.5f) .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.lazy.rememberLazyListState
import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -25,7 +26,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart import tachiyomi.presentation.core.util.isScrolledToStart
@ -69,8 +69,8 @@ fun <T> ListPreferenceWidget(
} }
} }
} }
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter)) if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter)) if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
} }
}, },
confirmButton = { 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.CheckBoxOutlineBlank
import androidx.compose.material.icons.rounded.DisabledByDefault import androidx.compose.material.icons.rounded.DisabledByDefault
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart import tachiyomi.presentation.core.util.isScrolledToStart
@ -115,8 +115,8 @@ fun <T> TriStateListDialog(
} }
} }
if (!listState.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter)) if (!listState.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!listState.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter)) 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 androidx.compose.runtime.Immutable
import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.presentation.more.stats.data.StatsData
sealed class StatsScreenState { sealed interface StatsScreenState {
@Immutable @Immutable
object Loading : StatsScreenState() data object Loading : StatsScreenState
@Immutable @Immutable
data class Success( data class Success(
@ -13,5 +13,5 @@ sealed class StatsScreenState {
val titles: StatsData.Titles, val titles: StatsData.Titles,
val chapters: StatsData.Chapters, val chapters: StatsData.Chapters,
val trackers: StatsData.Trackers, val trackers: StatsData.Trackers,
) : StatsScreenState() ) : StatsScreenState
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
package eu.kanade.presentation.reader.settings package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -30,35 +32,38 @@ fun ReaderSettingsDialog(
) )
val pagerState = rememberPagerState { tabTitles.size } val pagerState = rememberPagerState { tabTitles.size }
TabbedDialog( BoxWithConstraints {
onDismissRequest = { TabbedDialog(
onDismissRequest() modifier = Modifier.heightIn(max = maxHeight * 0.75f),
onShowMenus() onDismissRequest = {
}, onDismissRequest()
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)
onShowMenus() onShowMenus()
} },
} tabTitles = tabTitles,
pagerState = pagerState,
) { page ->
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
Column( LaunchedEffect(pagerState.currentPage) {
modifier = Modifier if (pagerState.currentPage == 2) {
.padding(vertical = TabbedDialogPaddings.Vertical) window?.setDimAmount(0f)
.verticalScroll(rememberScrollState()), onHideMenus()
) { } else {
when (page) { window?.setDimAmount(0.5f)
0 -> ReadingModePage(screenModel) onShowMenus()
1 -> GeneralPage(screenModel) }
2 -> ColorFilterPage(screenModel) }
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 package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope 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.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -8,7 +10,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.domain.manga.model.orientationType import eu.kanade.domain.manga.model.orientationType
import eu.kanade.domain.manga.model.readingModeType import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences 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 eu.kanade.tachiyomi.util.system.isReleaseBuildType
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem 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.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState
import java.text.NumberFormat import java.text.NumberFormat
private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it } private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it } private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it } private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it }
@Composable @Composable
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
@ -32,21 +34,25 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
val manga by screenModel.mangaFlow.collectAsState() val manga by screenModel.mangaFlow.collectAsState()
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) } val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
SelectItem( SettingsChipRow(R.string.pref_category_reading_mode) {
label = stringResource(R.string.pref_category_reading_mode), readingModeOptions.map { (stringRes, it) ->
options = readingModeOptions.map { stringResource(it.first) }.toTypedArray(), FilterChip(
selectedIndex = readingModeOptions.indexOfFirst { it.second == readingMode }, selected = it == readingMode,
) { onClick = { screenModel.onChangeReadingMode(it) },
screenModel.onChangeReadingMode(readingModeOptions[it].second) label = { Text(stringResource(stringRes)) },
)
}
} }
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) } val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
SelectItem( SettingsChipRow(R.string.rotation_type) {
label = stringResource(R.string.rotation_type), orientationTypeOptions.map { (stringRes, it) ->
options = orientationTypeOptions.map { stringResource(it.first) }.toTypedArray(), FilterChip(
selectedIndex = orientationTypeOptions.indexOfFirst { it.second == orientationType }, selected = it == orientationType,
) { onClick = { screenModel.onChangeOrientation(it) },
screenModel.onChangeOrientation(orientationTypeOptions[it].second) label = { Text(stringResource(stringRes)) },
)
}
} }
val viewer by screenModel.viewerFlow.collectAsState() val viewer by screenModel.viewerFlow.collectAsState()
@ -62,105 +68,74 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
HeadingItem(R.string.pager_viewer) HeadingItem(R.string.pager_viewer)
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState() val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
SelectItem( val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
label = stringResource(R.string.pref_viewer_nav), TapZonesItems(
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(), selected = navigationModePager,
selectedIndex = navigationModePager, onSelect = screenModel.preferences.navigationModePager()::set,
onSelect = { screenModel.preferences.navigationModePager().set(it) }, 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() val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
SelectItem( SettingsChipRow(R.string.pref_image_scale_type) {
label = stringResource(R.string.pref_image_scale_type), ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
options = ReaderPreferences.ImageScaleType.map { stringResource(it) }.toTypedArray(), FilterChip(
selectedIndex = imageScaleType - 1, selected = imageScaleType == index + 1,
onSelect = { screenModel.preferences.imageScaleType().set(it + 1) }, onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
) label = { Text(stringResource(it)) },
)
}
}
val zoomStart by screenModel.preferences.zoomStart().collectAsState() val zoomStart by screenModel.preferences.zoomStart().collectAsState()
SelectItem( SettingsChipRow(R.string.pref_zoom_start) {
label = stringResource(R.string.pref_zoom_start), ReaderPreferences.ZoomStart.mapIndexed { index, it ->
options = ReaderPreferences.ZoomStart.map { stringResource(it) }.toTypedArray(), FilterChip(
selectedIndex = zoomStart - 1, selected = zoomStart == index + 1,
onSelect = { screenModel.preferences.zoomStart().set(it + 1) }, onClick = { screenModel.preferences.zoomStart().set(index + 1) },
) label = { Text(stringResource(it)) },
)
}
}
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_crop_borders), label = stringResource(R.string.pref_crop_borders),
checked = cropBorders, pref = screenModel.preferences.cropBorders(),
onClick = {
screenModel.togglePreference(ReaderPreferences::cropBorders)
},
) )
val landscapeZoom by screenModel.preferences.landscapeZoom().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_landscape_zoom), label = stringResource(R.string.pref_landscape_zoom),
checked = landscapeZoom, pref = screenModel.preferences.landscapeZoom(),
onClick = {
screenModel.togglePreference(ReaderPreferences::landscapeZoom)
},
) )
val navigateToPan by screenModel.preferences.navigateToPan().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_navigate_pan), label = stringResource(R.string.pref_navigate_pan),
checked = navigateToPan, pref = screenModel.preferences.navigateToPan(),
onClick = {
screenModel.togglePreference(ReaderPreferences::navigateToPan)
},
) )
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState() val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_dual_page_split), label = stringResource(R.string.pref_dual_page_split),
checked = dualPageSplitPaged, pref = screenModel.preferences.dualPageSplitPaged(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageSplitPaged)
},
) )
if (dualPageSplitPaged) { if (dualPageSplitPaged) {
val dualPageInvertPaged by screenModel.preferences.dualPageInvertPaged().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_dual_page_invert), label = stringResource(R.string.pref_dual_page_invert),
checked = dualPageInvertPaged, pref = screenModel.preferences.dualPageInvertPaged(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageInvertPaged)
},
) )
} }
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState() val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_page_rotate), label = stringResource(R.string.pref_page_rotate),
checked = dualPageRotateToFit, pref = screenModel.preferences.dualPageRotateToFit(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFit)
},
) )
if (dualPageRotateToFit) { if (dualPageRotateToFit) {
val dualPageRotateToFitInvert by screenModel.preferences.dualPageRotateToFitInvert().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_page_rotate_invert), label = stringResource(R.string.pref_page_rotate_invert),
checked = dualPageRotateToFitInvert, pref = screenModel.preferences.dualPageRotateToFitInvert(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFitInvert)
},
) )
} }
} }
@ -172,25 +147,14 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
HeadingItem(R.string.webtoon_viewer) HeadingItem(R.string.webtoon_viewer)
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState() val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
SelectItem( val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
label = stringResource(R.string.pref_viewer_nav), TapZonesItems(
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(), selected = navigationModeWebtoon,
selectedIndex = navigationModeWebtoon, onSelect = screenModel.preferences.navigationModeWebtoon()::set,
onSelect = { screenModel.preferences.navigationModeWebtoon().set(it) }, 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() val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
SliderItem( SliderItem(
label = stringResource(R.string.pref_webtoon_side_padding), 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( CheckboxItem(
label = stringResource(R.string.pref_crop_borders), label = stringResource(R.string.pref_crop_borders),
checked = cropBordersWebtoon, pref = screenModel.preferences.cropBordersWebtoon(),
onClick = {
screenModel.togglePreference(ReaderPreferences::cropBordersWebtoon)
},
) )
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState() val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_dual_page_split), label = stringResource(R.string.pref_dual_page_split),
checked = dualPageSplitWebtoon, pref = screenModel.preferences.dualPageSplitWebtoon(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageSplitWebtoon)
},
) )
if (dualPageSplitWebtoon) { if (dualPageSplitWebtoon) {
val dualPageInvertWebtoon by screenModel.preferences.dualPageInvertWebtoon()
.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_dual_page_invert), label = stringResource(R.string.pref_dual_page_invert),
checked = dualPageInvertWebtoon, pref = screenModel.preferences.dualPageInvertWebtoon(),
onClick = {
screenModel.togglePreference(ReaderPreferences::dualPageInvertWebtoon)
},
) )
} }
if (!isReleaseBuildType) { if (!isReleaseBuildType) {
val longStripSplitWebtoon by screenModel.preferences.longStripSplitWebtoon()
.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_long_strip_split), label = stringResource(R.string.pref_long_strip_split),
checked = longStripSplitWebtoon, pref = screenModel.preferences.longStripSplitWebtoon(),
onClick = {
screenModel.togglePreference(ReaderPreferences::longStripSplitWebtoon)
},
) )
} }
val webtoonDoubleTapZoomEnabled by screenModel.preferences.webtoonDoubleTapZoomEnabled().collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_double_tap_zoom), label = stringResource(R.string.pref_double_tap_zoom),
checked = webtoonDoubleTapZoomEnabled, pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
onClick = {
screenModel.togglePreference(ReaderPreferences::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.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.data.track.TrackService
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.system.copyToClipboard 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 import java.text.DateFormat
private const val UnsetStatusTextAlpha = 0.5F private const val UnsetStatusTextAlpha = 0.5F
@ -98,7 +98,7 @@ fun TrackInfoDialogHome(
}, },
onChaptersClick = { onChapterClick(item) }, onChaptersClick = { onChapterClick(item) },
score = item.service.displayScore(item.track.toDbTrack()) score = item.service.displayScore(item.track.toDbTrack())
.takeIf { supportsScoring && item.track.score != 0F }, .takeIf { supportsScoring && item.track.score != 0.0 },
onScoreClick = { onScoreClick(item) } onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring }, .takeIf { supportsScoring },
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) } startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) }
@ -211,7 +211,7 @@ private fun TrackInfoItem(
} }
if (onStartDateClick != null && onEndDateClick != null) { if (onStartDateClick != null && onEndDateClick != null) {
Divider() HorizontalDivider()
Row(modifier = Modifier.height(IntrinsicSize.Min)) { Row(modifier = Modifier.height(IntrinsicSize.Min)) {
TrackDetailsItem( TrackDetailsItem(
modifier = Modifier.weight(1F), 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.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePicker
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.SelectableDates 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.WheelNumberPicker
import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.WheelTextPicker
import tachiyomi.presentation.core.components.material.AlertDialogContent 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.components.material.padding
import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart import tachiyomi.presentation.core.util.isScrolledToStart
@ -79,8 +79,8 @@ fun TrackStatusSelector(
} }
} }
} }
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter)) if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter)) if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}, },
onConfirm = onConfirm, onConfirm = onConfirm,
onDismissRequest = onDismissRequest, 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.material.icons.filled.Close
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme 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.R
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import tachiyomi.presentation.core.components.ScrollbarLazyColumn 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.Scaffold
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
@ -143,7 +143,7 @@ fun TrackServiceSearch(
} }
}, },
) )
Divider() HorizontalDivider()
} }
}, },
bottomBar = { bottomBar = {

View File

@ -28,7 +28,7 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem 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.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.FastScrollLazyColumn
@ -40,10 +40,9 @@ import kotlin.time.Duration.Companion.seconds
@Composable @Composable
fun UpdateScreen( fun UpdateScreen(
state: UpdatesState, state: UpdatesScreenModel.State,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
lastUpdated: Long, lastUpdated: Long,
relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit, onClickCover: (UpdatesItem) -> Unit,
onSelectAll: (Boolean) -> Unit, onSelectAll: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
@ -114,7 +113,7 @@ fun UpdateScreen(
} }
updatesUiItems( updatesUiItems(
uiModels = state.getUiModel(context, relativeTime), uiModels = state.getUiModel(context),
selectionMode = state.selectionMode, selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected, onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover, onClickCover = onClickCover,
@ -209,7 +208,7 @@ private fun UpdatesBottomBar(
) )
} }
sealed class UpdatesUiModel { sealed interface UpdatesUiModel {
data class Header(val date: String) : UpdatesUiModel() data class Header(val date: String) : UpdatesUiModel
data class Item(val item: UpdatesItem) : 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.graphics.Bitmap
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box 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.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.google.accompanist.web.AccompanistWebViewClient import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.LoadingState import com.google.accompanist.web.LoadingState
@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator
import com.google.accompanist.web.rememberWebViewState import com.google.accompanist.web.rememberWebViewState
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getHtml
import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.setDefaultSettings
import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@Composable @Composable
@ -46,7 +52,53 @@ fun WebViewScreenContent(
) { ) {
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers) val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
val navigator = rememberWebViewNavigator() val navigator = rememberWebViewNavigator()
val uriHandler = LocalUriHandler.current
val scope = rememberCoroutineScope()
var currentUrl by remember { mutableStateOf(url) } 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( Scaffold(
topBar = { topBar = {
@ -116,61 +168,38 @@ fun WebViewScreenContent(
} }
}, },
) { contentPadding -> ) { contentPadding ->
val webClient = remember { Column(
object : AccompanistWebViewClient() { modifier = Modifier.padding(contentPadding),
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { ) {
super.onPageStarted(view, url, favicon) if (showCloudflareHelp) {
url?.let { WarningBanner(
currentUrl = it textRes = R.string.information_cloudflare_help,
onUrlChange(it) modifier = Modifier.clickable {
} uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
} },
)
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)
}
} }
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 * @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.core.content.ContextCompat
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import com.squareup.sqldelight.android.AndroidSqliteDriver import app.cash.sqldelight.db.SqlDriver
import com.squareup.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
@ -39,11 +39,11 @@ import tachiyomi.core.provider.AndroidDownloadFolderProvider
import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.Database import tachiyomi.data.Database
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.data.DateColumnAdapter
import tachiyomi.data.History import tachiyomi.data.History
import tachiyomi.data.Mangas import tachiyomi.data.Mangas
import tachiyomi.data.dateAdapter import tachiyomi.data.StringListColumnAdapter
import tachiyomi.data.listOfStringsAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.data.updateStrategyAdapter
import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
@ -92,11 +92,11 @@ class AppModule(val app: Application) : InjektModule {
Database( Database(
driver = get(), driver = get(),
historyAdapter = History.Adapter( historyAdapter = History.Adapter(
last_readAdapter = dateAdapter, last_readAdapter = DateColumnAdapter,
), ),
mangasAdapter = Mangas.Adapter( mangasAdapter = Mangas.Adapter(
genreAdapter = listOfStringsAdapter, genreAdapter = StringListColumnAdapter,
update_strategyAdapter = updateStrategyAdapter, 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.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences 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.DeviceUtil
import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import eu.kanade.tachiyomi.util.system.toast 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.PreferenceStore
import tachiyomi.core.preference.TriState import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getEnum import tachiyomi.core.preference.getEnum
import tachiyomi.core.preference.minusAssign
import tachiyomi.core.preference.plusAssign
import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ data class BackupTracking(
title = this@BackupTracking.title, title = this@BackupTracking.title,
lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(), lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(),
totalChapters = this@BackupTracking.totalChapters.toLong(), totalChapters = this@BackupTracking.totalChapters.toLong(),
score = this@BackupTracking.score, score = this@BackupTracking.score.toDouble(),
status = this@BackupTracking.status.toLong(), status = this@BackupTracking.status.toLong(),
startDate = this@BackupTracking.startedReadingDate, startDate = this@BackupTracking.startedReadingDate,
finishDate = this@BackupTracking.finishedReadingDate, finishDate = this@BackupTracking.finishedReadingDate,
@ -54,7 +54,7 @@ data class BackupTracking(
} }
val backupTrackMapper = { 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( BackupTracking(
syncId = syncId.toInt(), syncId = syncId.toInt(),
mediaId = mediaId, mediaId = mediaId,
@ -63,7 +63,7 @@ val backupTrackMapper = {
title = title, title = title,
lastChapterRead = lastChapterRead.toFloat(), lastChapterRead = lastChapterRead.toFloat(),
totalChapters = totalChapters.toInt(), totalChapters = totalChapters.toInt(),
score = score, score = score.toFloat(),
status = status.toInt(), status = status.toInt(),
startedReadingDate = startDate, startedReadingDate = startDate,
finishedReadingDate = finishDate, finishedReadingDate = finishDate,

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.model.Chapter 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 eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.interactor.GetChapter 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_HAS_UNREAD
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED 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_NON_READ
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.manga.interactor.GetLibraryManga import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.SetFetchInterval
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.model.SourceNotInstalledException 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.time.ZonedDateTime
import java.util.Date import java.util.Date
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit 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 getTracks: GetTracks = Injekt.get()
private val insertTrack: InsertTrack = Injekt.get() private val insertTrack: InsertTrack = Injekt.get()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get() private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
private val setFetchInterval: SetFetchInterval = Injekt.get()
private val notifier = LibraryUpdateNotifier(context) private val notifier = LibraryUpdateNotifier(context)
@ -227,6 +231,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val hasDownloads = AtomicBoolean(false) val hasDownloads = AtomicBoolean(false)
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get() val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
val fetchWindow by lazy { setFetchInterval.getWindow(ZonedDateTime.now()) }
coroutineScope { coroutineScope {
mangaToUpdate.groupBy { it.manga.source }.values mangaToUpdate.groupBy { it.manga.source }.values
.map { mangaInSource -> .map { mangaInSource ->
@ -247,6 +253,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
manga, manga,
) { ) {
when { 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 -> MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_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 -> MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started)) skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
manga.updateStrategy != UpdateStrategy.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_always_update)) skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
else -> { else -> {
try { try {
val newChapters = updateManga(manga) val newChapters = updateManga(manga, fetchWindow)
.sortedByDescending { it.sourceOrder } .sortedByDescending { it.sourceOrder }
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {
@ -317,6 +326,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
) )
} }
if (skippedUpdates.isNotEmpty()) { 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) notifier.showUpdateSkippedNotification(skippedUpdates.size)
} }
} }
@ -333,7 +349,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
* @param manga the manga to update. * @param manga the manga to update.
* @return a pair of the inserted and removed chapters. * @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) val source = sourceManager.getOrStub(manga.source)
// Update manga metadata if needed // 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 // to get latest data so it doesn't get overwritten later on
val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList() 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() { private suspend fun updateCovers() {

View File

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

View File

@ -152,8 +152,8 @@ sealed class Image(
} }
} }
sealed class Location { sealed interface Location {
data class Pictures private constructor(val relativePath: String) : Location() { data class Pictures private constructor(val relativePath: String) : Location {
companion object { companion object {
fun create(relativePath: String = ""): Pictures { fun create(relativePath: String = ""): Pictures {
return Pictures(relativePath) return Pictures(relativePath)
@ -161,7 +161,7 @@ sealed class Location {
} }
} }
object Cache : Location() data object Cache : Location
fun directory(context: Context): File { fun directory(context: Context): File {
return when (this) { 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.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import logcat.LogPriority import logcat.LogPriority
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -20,10 +21,12 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.time.ZoneOffset
import tachiyomi.domain.track.model.Track as DomainTrack import tachiyomi.domain.track.model.Track as DomainTrack
abstract class TrackService(val id: Long) { abstract class TrackService(val id: Long) {
@ -62,7 +65,7 @@ abstract class TrackService(val id: Long) {
abstract fun getScoreList(): List<String> abstract fun getScoreList(): List<String>
// TODO: Store all scores as 10 point in the future maybe? // 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 return track.score
} }
@ -107,7 +110,7 @@ abstract class TrackService(val id: Long) {
val hasReadChapters = allChapters.any { it.read } val hasReadChapters = allChapters.any { it.read }
bind(item, hasReadChapters) bind(item, hasReadChapters)
val track = item.toDomainTrack(idRequired = false) ?: return@withIOContext var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
Injekt.get<InsertTrack>().await(track) Injekt.get<InsertTrack>().await(track)
@ -120,10 +123,25 @@ abstract class TrackService(val id: Long) {
?.chapterNumber?.toDouble() ?: -1.0 ?.chapterNumber?.toDouble() ?: -1.0
if (latestLocalReadChapterNumber > track.lastChapterRead) { if (latestLocalReadChapterNumber > track.lastChapterRead) {
val updatedTrack = track.copy( track = track.copy(
lastChapterRead = latestLocalReadChapterNumber, 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) { 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.status = getReadingStatus()
} }
track.last_chapter_read = chapterNumber.toFloat() 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.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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 // Score is stored in 100 point format
return track.score / 10f return track.score / 10.0
} }
override fun indexToScore(index: Int): Float { 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.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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.POST
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.contentOrNull

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.bangumi package eu.kanade.tachiyomi.data.track.bangumi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Interceptor import okhttp3.Interceptor
@ -67,7 +66,7 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
private fun addToken(token: String, oidFormBody: FormBody): FormBody { private fun addToken(token: String, oidFormBody: FormBody): FormBody {
val newFormBody = FormBody.Builder() 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(oidFormBody.name(i), oidFormBody.value(i))
} }
newFormBody.add("access_token", token) 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.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kitsu package eu.kanade.tachiyomi.data.track.kitsu
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response 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.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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.DeletableTrackService
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.shikimori package eu.kanade.tachiyomi.data.track.shikimori
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response 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.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.preference.plusAssign
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -19,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.preference.plusAssign
import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
@ -238,10 +238,10 @@ class ExtensionManager(
/** /**
* Uninstalls the extension that matches the given package name. * 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) { fun uninstallExtension(extension: Extension) {
installer.uninstallApk(pkgName) installer.uninstallApk(extension.pkgName)
} }
/** /**
@ -260,18 +260,13 @@ class ExtensionManager(
val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature } val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
_untrustedExtensionsFlow.value -= nowTrustedExtensions _untrustedExtensionsFlow.value -= nowTrustedExtensions
val ctx = context
launchNow { launchNow {
nowTrustedExtensions nowTrustedExtensions
.map { extension -> .map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) } async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.map { it.await() }
.forEach { result ->
if (result is LoadResult.Success) {
registerNewExtension(result.extension)
}
} }
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
} }
} }

View File

@ -125,7 +125,7 @@ internal class ExtensionGithubApi {
hasChangelog = it.hasChangelog == 1, hasChangelog = it.hasChangelog == 1,
sources = it.sources?.map(extensionSourceMapper).orEmpty(), sources = it.sources?.map(extensionSourceMapper).orEmpty(),
apkName = it.apk, 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 package eu.kanade.tachiyomi.extension.model
sealed class LoadResult { sealed interface LoadResult {
class Success(val extension: Extension.Installed) : LoadResult() data class Success(val extension: Extension.Installed) : LoadResult
class Untrusted(val extension: Extension.Untrusted) : LoadResult() data class Untrusted(val extension: Extension.Untrusted) : LoadResult
object Error : LoadResult() data object Error : LoadResult
} }

View File

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

View File

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

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