Merge branch 'master' into sync-part-1

This commit is contained in:
Aria Moradi 2023-07-11 13:43:29 +03:30
commit 1af1ce924d
105 changed files with 1320 additions and 1698 deletions

13
.github/renovate.json vendored
View File

@ -1,13 +0,0 @@
{
"extends": [
"config:base"
],
"schedule": ["every sunday"],
"packageRules": [
{
"managers": ["maven"],
"packageNames": ["com.google.guava:guava"],
"versionScheme": "docker"
}
]
}

22
.github/renovate.json5 vendored Normal file
View File

@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"schedule": ["every sunday"],
"packageRules": [
{
"managers": ["maven"],
"packageNames": ["com.google.guava:guava"],
"versionScheme": "docker"
},
{
// Compiler plugins are tightly coupled to Kotlin version
"groupName": "Kotlin",
"matchPackagePrefixes": [
"androidx.compose.compiler",
"org.jetbrains.kotlin",
],
}
]
}

View File

@ -23,7 +23,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi" applicationId = "eu.kanade.tachiyomi"
versionCode = 103 versionCode = 104
versionName = "0.14.6" versionName = "0.14.6"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@ -239,6 +239,7 @@ dependencies {
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion) implementation(libs.compose.materialmotion)
implementation(libs.compose.simpleicons) implementation(libs.compose.simpleicons)
implementation(libs.swipe)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)

View File

@ -16,6 +16,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.track.interactor.TrackChapter
import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl import tachiyomi.data.history.HistoryRepositoryImpl
@ -109,6 +110,7 @@ class DomainModule : InjektModule {
addFactory { GetApplicationRelease(get(), get()) } addFactory { GetApplicationRelease(get(), get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) } addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) } addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(get()) } addFactory { GetTracksPerManga(get()) }
addFactory { GetTracks(get()) } addFactory { GetTracks(get()) }

View File

@ -7,9 +7,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
import tachiyomi.core.preference.TriState
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 tachiyomi.domain.manga.model.TriStateFilter
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -20,19 +20,19 @@ val Manga.readingModeType: Long
val Manga.orientationType: Long val Manga.orientationType: Long
get() = viewerFlags and OrientationType.MASK.toLong() get() = viewerFlags and OrientationType.MASK.toLong()
val Manga.downloadedFilter: TriStateFilter val Manga.downloadedFilter: TriState
get() { get() {
if (forceDownloaded()) return TriStateFilter.ENABLED_IS if (forceDownloaded()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) { return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
else -> TriStateFilter.DISABLED else -> TriState.DISABLED
} }
} }
fun Manga.chaptersFiltered(): Boolean { fun Manga.chaptersFiltered(): Boolean {
return unreadFilter != TriStateFilter.DISABLED || return unreadFilter != TriState.DISABLED ||
downloadedFilter != TriStateFilter.DISABLED || downloadedFilter != TriState.DISABLED ||
bookmarkedFilter != TriStateFilter.DISABLED bookmarkedFilter != TriState.DISABLED
} }
fun Manga.forceDownloaded(): Boolean { fun Manga.forceDownloaded(): Boolean {
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get() return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()

View File

@ -0,0 +1,56 @@
package eu.kanade.domain.track.interactor
import android.content.Context
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
import eu.kanade.domain.track.store.DelayedTrackingStore
import eu.kanade.tachiyomi.data.track.TrackManager
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import logcat.LogPriority
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
class TrackChapter(
private val getTracks: GetTracks,
private val trackManager: TrackManager,
private val insertTrack: InsertTrack,
private val delayedTrackingStore: DelayedTrackingStore,
) {
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope {
launchNonCancellable {
val tracks = getTracks.await(mangaId)
if (tracks.isEmpty()) return@launchNonCancellable
tracks.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged && chapterNumber > track.lastChapterRead) {
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
async {
runCatching {
try {
service.update(updatedTrack.toDbTrack(), true)
insertTrack.await(updatedTrack)
} catch (e: Exception) {
delayedTrackingStore.addItem(updatedTrack)
DelayedTrackingUpdateJob.setupTask(context)
throw e
}
}
}
} else {
null
}
}
.awaitAll()
.mapNotNull { it.exceptionOrNull() }
.forEach { logcat(LogPriority.INFO, it) }
}
}
}

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.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -19,7 +20,6 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
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.LazyColumn
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

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues 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.GlobalSearchCardRow
@ -14,7 +15,6 @@ import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
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.LazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@Composable @Composable

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse.components package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.PaddingValues 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.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -12,7 +13,6 @@ import eu.kanade.presentation.library.components.MangaListItem
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
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.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.plus
@Composable @Composable

View File

@ -3,6 +3,7 @@ package eu.kanade.presentation.category
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
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.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -16,7 +17,6 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.category.CategoryScreenState import eu.kanade.tachiyomi.ui.category.CategoryScreenState
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.presentation.core.components.LazyColumn
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.components.material.topSmallPaddingValues import tachiyomi.presentation.core.components.material.topSmallPaddingValues

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.TextFieldDefaults import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
@ -38,12 +39,14 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -62,7 +65,7 @@ fun AppBar(
subtitle: String? = null, subtitle: String? = null,
// Up button // Up button
navigateUp: (() -> Unit)? = null, navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Outlined.ArrowBack, navigationIcon: ImageVector? = null,
// Menu // Menu
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
// Action mode // Action mode
@ -107,7 +110,7 @@ fun AppBar(
titleContent: @Composable () -> Unit, titleContent: @Composable () -> Unit,
// Up button // Up button
navigateUp: (() -> Unit)? = null, navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Outlined.ArrowBack, navigationIcon: ImageVector? = null,
// Menu // Menu
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
// Action mode // Action mode
@ -131,10 +134,7 @@ fun AppBar(
} else { } else {
navigateUp?.let { navigateUp?.let {
IconButton(onClick = it) { IconButton(onClick = it) {
Icon( UpIcon(navigationIcon)
imageVector = navigationIcon,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
} }
} }
} }
@ -360,6 +360,16 @@ fun SearchToolbar(
) )
} }
@Composable
fun UpIcon(navigationIcon: ImageVector? = null) {
val icon = navigationIcon
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) Icons.Outlined.ArrowBack else Icons.Outlined.ArrowForward
Icon(
imageVector = icon,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
}
sealed interface AppBar { sealed interface AppBar {
sealed interface AppBarAction sealed interface AppBarAction

View File

@ -1,128 +0,0 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckBox
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
import androidx.compose.material.icons.rounded.DisabledByDefault
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.presentation.core.components.SettingsItemsPaddings
@Composable
fun TriStateItem(
label: String,
state: TriStateFilter,
enabled: Boolean = true,
onClick: ((TriStateFilter) -> Unit)?,
) {
Row(
modifier = Modifier
.clickable(
enabled = enabled && onClick != null,
onClick = {
when (state) {
TriStateFilter.DISABLED -> onClick?.invoke(TriStateFilter.ENABLED_IS)
TriStateFilter.ENABLED_IS -> onClick?.invoke(TriStateFilter.ENABLED_NOT)
TriStateFilter.ENABLED_NOT -> onClick?.invoke(TriStateFilter.DISABLED)
}
},
)
.fillMaxWidth()
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(24.dp),
) {
val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled
Icon(
imageVector = when (state) {
TriStateFilter.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank
TriStateFilter.ENABLED_IS -> Icons.Rounded.CheckBox
TriStateFilter.ENABLED_NOT -> Icons.Rounded.DisabledByDefault
},
contentDescription = null,
tint = if (!enabled || state == TriStateFilter.DISABLED) {
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha)
} else {
when (onClick) {
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled)
else -> MaterialTheme.colorScheme.primary
}
},
)
Text(
text = label,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha),
style = MaterialTheme.typography.bodyMedium,
)
}
}
@Composable
fun SelectItem(
label: String,
options: Array<out Any?>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
) {
OutlinedTextField(
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
label = { Text(text = label) },
value = options[selectedIndex].toString(),
onValueChange = {},
readOnly = true,
singleLine = true,
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded,
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
ExposedDropdownMenu(
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
expanded = expanded,
onDismissRequest = { expanded = false },
) {
options.forEachIndexed { index, text ->
DropdownMenuItem(
text = { Text(text.toString()) },
onClick = {
onSelect(index)
expanded = false
},
)
}
}
}
}

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentSize
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
@ -42,13 +43,13 @@ fun TabbedDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
tabTitles: List<String>, tabTitles: List<String>,
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null, tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
pagerState: PagerState = rememberPagerState { tabTitles.size },
content: @Composable (Int) -> Unit, content: @Composable (Int) -> Unit,
) { ) {
AdaptiveSheet( AdaptiveSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val pagerState = rememberPagerState { tabTitles.size }
Column { Column {
Row { Row {

View File

@ -14,21 +14,21 @@ 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.components.TriStateItem
import eu.kanade.presentation.util.collectAsState 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.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.sort import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.TriStateFilter
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.RadioItem import tachiyomi.presentation.core.components.RadioItem
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
@Composable @Composable
fun LibrarySettingsDialog( fun LibrarySettingsDialog(
@ -74,7 +74,7 @@ private fun ColumnScope.FilterPage(
TriStateItem( TriStateItem(
label = stringResource(R.string.label_downloaded), label = stringResource(R.string.label_downloaded),
state = if (downloadedOnly) { state = if (downloadedOnly) {
TriStateFilter.ENABLED_IS TriState.ENABLED_IS
} else { } else {
filterDownloaded filterDownloaded
}, },

View File

@ -27,20 +27,20 @@ import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.forceDownloaded import eu.kanade.domain.manga.model.forceDownloaded
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.components.TriStateItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.core.preference.TriState
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.presentation.core.components.RadioItem import tachiyomi.presentation.core.components.RadioItem
import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
@Composable @Composable
fun ChapterSettingsDialog( fun ChapterSettingsDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
manga: Manga? = null, manga: Manga? = null,
onDownloadFilterChanged: (TriStateFilter) -> Unit, onDownloadFilterChanged: (TriState) -> Unit,
onUnreadFilterChanged: (TriStateFilter) -> Unit, onUnreadFilterChanged: (TriState) -> Unit,
onBookmarkedFilterChanged: (TriStateFilter) -> Unit, onBookmarkedFilterChanged: (TriState) -> Unit,
onSortModeChanged: (Long) -> Unit, onSortModeChanged: (Long) -> Unit,
onDisplayModeChanged: (Long) -> Unit, onDisplayModeChanged: (Long) -> Unit,
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit, onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
@ -78,11 +78,11 @@ fun ChapterSettingsDialog(
when (page) { when (page) {
0 -> { 0 -> {
FilterPage( FilterPage(
downloadFilter = manga?.downloadedFilter ?: TriStateFilter.DISABLED, downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true }, onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
unreadFilter = manga?.unreadFilter ?: TriStateFilter.DISABLED, unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
onUnreadFilterChanged = onUnreadFilterChanged, onUnreadFilterChanged = onUnreadFilterChanged,
bookmarkedFilter = manga?.bookmarkedFilter ?: TriStateFilter.DISABLED, bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
onBookmarkedFilterChanged = onBookmarkedFilterChanged, onBookmarkedFilterChanged = onBookmarkedFilterChanged,
) )
} }
@ -106,12 +106,12 @@ fun ChapterSettingsDialog(
@Composable @Composable
private fun ColumnScope.FilterPage( private fun ColumnScope.FilterPage(
downloadFilter: TriStateFilter, downloadFilter: TriState,
onDownloadFilterChanged: ((TriStateFilter) -> Unit)?, onDownloadFilterChanged: ((TriState) -> Unit)?,
unreadFilter: TriStateFilter, unreadFilter: TriState,
onUnreadFilterChanged: (TriStateFilter) -> Unit, onUnreadFilterChanged: (TriState) -> Unit,
bookmarkedFilter: TriStateFilter, bookmarkedFilter: TriState,
onBookmarkedFilterChanged: (TriStateFilter) -> Unit, onBookmarkedFilterChanged: (TriState) -> Unit,
) { ) {
TriStateItem( TriStateItem(
label = stringResource(R.string.label_downloaded), label = stringResource(R.string.label_downloaded),

View File

@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -69,7 +70,6 @@ import tachiyomi.domain.chapter.service.missingChaptersCount
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.model.StubSource
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.TwoPanelBox import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.VerticalFastScroller import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
@ -87,8 +87,8 @@ fun MangaScreen(
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -141,8 +141,8 @@ fun MangaScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
@ -175,8 +175,8 @@ fun MangaScreen(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat, dateFormat = dateFormat,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
@ -214,8 +214,8 @@ private fun MangaScreenSmallImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -258,7 +258,7 @@ private fun MangaScreenSmallImpl(
) { ) {
val chapterListState = rememberLazyListState() val chapterListState = rememberLazyListState()
val chapters = remember(state) { state.processedChapters.toList() } val chapters = remember(state) { state.processedChapters }
val internalOnBackPressed = { val internalOnBackPressed = {
if (chapters.fastAny { it.selected }) { if (chapters.fastAny { it.selected }) {
@ -320,7 +320,7 @@ private fun MangaScreenSmallImpl(
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { text = {
val id = if (chapters.fastAny { it.chapter.read }) { val id = if (state.chapters.fastAny { it.chapter.read }) {
R.string.action_resume R.string.action_resume
} else { } else {
R.string.action_start R.string.action_start
@ -421,8 +421,8 @@ private fun MangaScreenSmallImpl(
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected, onChapterSelected = onChapterSelected,
@ -440,8 +440,8 @@ fun MangaScreenLargeImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -485,7 +485,7 @@ fun MangaScreenLargeImpl(
val layoutDirection = LocalLayoutDirection.current val layoutDirection = LocalLayoutDirection.current
val density = LocalDensity.current val density = LocalDensity.current
val chapters = remember(state) { state.processedChapters.toList() } val chapters = remember(state) { state.processedChapters }
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
var topBarHeight by remember { mutableIntStateOf(0) } var topBarHeight by remember { mutableIntStateOf(0) }
@ -555,7 +555,7 @@ fun MangaScreenLargeImpl(
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { text = {
val id = if (chapters.fastAny { it.chapter.read }) { val id = if (state.chapters.fastAny { it.chapter.read }) {
R.string.action_resume R.string.action_resume
} else { } else {
R.string.action_start R.string.action_start
@ -641,8 +641,8 @@ fun MangaScreenLargeImpl(
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected, onChapterSelected = onChapterSelected,
@ -703,8 +703,8 @@ private fun LazyListScope.sharedChapterItems(
chapters: List<ChapterItem>, chapters: List<ChapterItem>,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
@ -751,8 +751,8 @@ private fun LazyListScope.sharedChapterItems(
downloadIndicatorEnabled = chapters.fastAll { !it.selected }, downloadIndicatorEnabled = chapters.fastAll { !it.selected },
downloadStateProvider = { chapterItem.downloadState }, downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress }, downloadProgressProvider = { chapterItem.downloadProgress },
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = { onLongClick = {
onChapterSelected(chapterItem, !chapterItem.selected, true, true) onChapterSelected(chapterItem, !chapterItem.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)

View File

@ -1,20 +1,12 @@
package eu.kanade.presentation.manga.components package eu.kanade.presentation.manga.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
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.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissValue
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.filled.Circle
@ -25,7 +17,6 @@ import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FileDownloadOff import androidx.compose.material.icons.outlined.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
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
@ -37,14 +28,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -53,10 +48,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
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 me.saket.swipe.SwipeableActionsBox
import me.saket.swipe.rememberSwipeableActionsState
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable @Composable
fun MangaChapterListItem( fun MangaChapterListItem(
@ -71,13 +69,19 @@ fun MangaChapterListItem(
downloadIndicatorEnabled: Boolean, downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State, downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onDownloadClick: ((ChapterDownloadAction) -> Unit)?, onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
) { ) {
val haptic = LocalHapticFeedback.current
val density = LocalDensity.current
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
// Increase touch slop of swipe action to reduce accidental trigger // Increase touch slop of swipe action to reduce accidental trigger
val configuration = LocalViewConfiguration.current val configuration = LocalViewConfiguration.current
CompositionLocalProvider( CompositionLocalProvider(
@ -85,247 +89,188 @@ fun MangaChapterListItem(
override val touchSlop: Float = configuration.touchSlop * 3f override val touchSlop: Float = configuration.touchSlop * 3f
}, },
) { ) {
val textAlpha = if (read) ReadItemAlpha else 1f val start = getSwipeAction(
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha action = chapterSwipeStartAction,
read = read,
val chapterSwipeStartEnabled = chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled bookmark = bookmark,
val chapterSwipeEndEnabled = chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
val dismissState = rememberDismissState() onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
val dismissDirections = remember { mutableSetOf<DismissDirection>() }
var lastDismissDirection: DismissDirection? by remember { mutableStateOf(null) }
if (lastDismissDirection == null) {
if (chapterSwipeStartEnabled) {
dismissDirections.add(DismissDirection.EndToStart)
}
if (chapterSwipeEndEnabled) {
dismissDirections.add(DismissDirection.StartToEnd)
}
}
val animateDismissContentAlpha by animateFloatAsState(
label = "animateDismissContentAlpha",
targetValue = if (lastDismissDirection != null) 1f else 0f,
animationSpec = tween(durationMillis = if (lastDismissDirection != null) 500 else 0),
finishedListener = {
lastDismissDirection = null
},
) )
val dismissContentAlpha = if (lastDismissDirection != null) animateDismissContentAlpha else 1f val end = getSwipeAction(
val backgroundColor = if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) { action = chapterSwipeEndAction,
MaterialTheme.colorScheme.primary read = read,
} else if (chapterSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) { bookmark = bookmark,
MaterialTheme.colorScheme.primary downloadState = downloadStateProvider(),
} else { background = MaterialTheme.colorScheme.primaryContainer,
Color.Unspecified onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
)
val swipeableActionsState = rememberSwipeableActionsState()
LaunchedEffect(Unit) {
// Haptic effect when swipe over threshold
val swipeActionThresholdPx = with(density) { swipeActionThreshold.toPx() }
snapshotFlow { swipeableActionsState.offset.value.absoluteValue > swipeActionThresholdPx }
.collect { if (it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }
} }
LaunchedEffect(dismissState.currentValue) { SwipeableActionsBox(
when (dismissState.currentValue) { modifier = Modifier.clipToBounds(),
DismissValue.DismissedToEnd -> { state = swipeableActionsState,
lastDismissDirection = DismissDirection.StartToEnd startActions = listOfNotNull(start),
val dismissDirectionsCopy = dismissDirections.toSet() endActions = listOfNotNull(end),
dismissDirections.clear() swipeThreshold = swipeActionThreshold,
onChapterSwipe(chapterSwipeEndAction) backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
dismissState.snapTo(DismissValue.Default) ) {
dismissDirections.addAll(dismissDirectionsCopy) Row(
} modifier = modifier
DismissValue.DismissedToStart -> { .selectedBackground(selected)
lastDismissDirection = DismissDirection.EndToStart .combinedClickable(
val dismissDirectionsCopy = dismissDirections.toSet() onClick = onClick,
dismissDirections.clear() onLongClick = onLongClick,
onChapterSwipe(chapterSwipeStartAction) )
dismissState.snapTo(DismissValue.Default) .padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
dismissDirections.addAll(dismissDirectionsCopy) ) {
} Column(
DismissValue.Default -> { } modifier = Modifier.weight(1f),
} verticalArrangement = Arrangement.spacedBy(6.dp),
}
SwipeToDismiss(
state = dismissState,
directions = dismissDirections,
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor),
) { ) {
if (dismissState.dismissDirection in dismissDirections) { Row(
val downloadState = downloadStateProvider() horizontalArrangement = Arrangement.spacedBy(2.dp),
SwipeBackgroundIcon( verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(start = 16.dp)
.align(Alignment.CenterStart)
.alpha(
if (dismissState.dismissDirection == DismissDirection.StartToEnd) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = chapterSwipeEndAction,
read = read,
bookmark = bookmark,
downloadState = downloadState,
)
SwipeBackgroundIcon(
modifier = Modifier
.padding(end = 16.dp)
.align(Alignment.CenterEnd)
.alpha(
if (dismissState.dismissDirection == DismissDirection.EndToStart) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = chapterSwipeStartAction,
read = read,
bookmark = bookmark,
downloadState = downloadState,
)
}
}
},
dismissContent = {
Row(
modifier = modifier
.background(
MaterialTheme.colorScheme.background.copy(dismissContentAlpha),
)
.selectedBackground(selected)
.alpha(dismissContentAlpha)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(6.dp),
) { ) {
Row( var textHeight by remember { mutableIntStateOf(0) }
horizontalArrangement = Arrangement.spacedBy(2.dp), if (!read) {
verticalAlignment = Alignment.CenterVertically, Icon(
) { imageVector = Icons.Filled.Circle,
var textHeight by remember { mutableIntStateOf(0) } contentDescription = stringResource(R.string.unread),
if (!read) { modifier = Modifier
Icon( .height(8.dp)
imageVector = Icons.Filled.Circle, .padding(end = 4.dp),
contentDescription = stringResource(R.string.unread), tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.height(8.dp)
.padding(end = 4.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
) )
} }
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
)
}
Row { Row {
ProvideTextStyle( ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy( value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp, fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha), color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
), ),
) { ) {
if (date != null) { if (date != null) {
Text( Text(
text = date, text = date,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
if (readProgress != null || scanlator != null) DotSeparatorText() if (readProgress != null || scanlator != null) DotSeparatorText()
} }
if (readProgress != null) { if (readProgress != null) {
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha), modifier = Modifier.alpha(ReadItemAlpha),
) )
if (scanlator != null) DotSeparatorText() if (scanlator != null) DotSeparatorText()
} }
if (scanlator != null) { if (scanlator != null) {
Text( Text(
text = scanlator, text = scanlator,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
}
} }
} }
} }
if (onDownloadClick != null) {
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
} }
},
) if (onDownloadClick != null) {
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
}
}
} }
} }
@Composable private fun getSwipeAction(
private fun SwipeBackgroundIcon( action: LibraryPreferences.ChapterSwipeAction,
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.ChapterSwipeAction,
read: Boolean, read: Boolean,
bookmark: Boolean, bookmark: Boolean,
downloadState: Download.State, downloadState: Download.State,
) { background: Color,
val imageVector = when (swipeAction) { onSwipe: () -> Unit,
LibraryPreferences.ChapterSwipeAction.ToggleRead -> { ): me.saket.swipe.SwipeAction? {
if (!read) { return when (action) {
Icons.Outlined.Done LibraryPreferences.ChapterSwipeAction.ToggleRead -> swipeAction(
} else { icon = if (!read) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
Icons.Outlined.RemoveDone background = background,
} isUndo = read,
} onSwipe = onSwipe,
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> { )
if (!bookmark) { LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> swipeAction(
Icons.Outlined.BookmarkAdd icon = if (!bookmark) Icons.Outlined.BookmarkAdd else Icons.Outlined.BookmarkRemove,
} else { background = background,
Icons.Outlined.BookmarkRemove isUndo = bookmark,
} onSwipe = onSwipe,
} )
LibraryPreferences.ChapterSwipeAction.Download -> { LibraryPreferences.ChapterSwipeAction.Download -> swipeAction(
when (downloadState) { icon = when (downloadState) {
Download.State.NOT_DOWNLOADED, Download.State.NOT_DOWNLOADED, Download.State.ERROR -> Icons.Outlined.Download
Download.State.ERROR, Download.State.QUEUE, Download.State.DOWNLOADING -> Icons.Outlined.FileDownloadOff
-> { Icons.Outlined.Download } Download.State.DOWNLOADED -> Icons.Outlined.Delete
Download.State.QUEUE, },
Download.State.DOWNLOADING, background = background,
-> { Icons.Outlined.FileDownloadOff } onSwipe = onSwipe,
Download.State.DOWNLOADED -> { Icons.Outlined.Delete } )
}
}
LibraryPreferences.ChapterSwipeAction.Disabled -> null LibraryPreferences.ChapterSwipeAction.Disabled -> null
} }
imageVector?.let {
Icon(
modifier = modifier,
imageVector = imageVector,
tint = tint,
contentDescription = null,
)
}
} }
private fun swipeAction(
onSwipe: () -> Unit,
icon: ImageVector,
background: Color,
isUndo: Boolean = false,
): me.saket.swipe.SwipeAction {
return me.saket.swipe.SwipeAction(
icon = {
Icon(
modifier = Modifier.padding(16.dp),
imageVector = icon,
tint = contentColorFor(background),
contentDescription = null,
)
},
background = background,
onSwipe = onSwipe,
isUndo = isUndo,
)
}
private val swipeActionThreshold = 56.dp

View File

@ -2,13 +2,11 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -29,6 +27,7 @@ import androidx.compose.ui.unit.dp
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.DownloadDropdownMenu import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.theme.active import tachiyomi.presentation.core.theme.active
@ -67,10 +66,7 @@ fun MangaToolbar(
}, },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBackClicked) { IconButton(onClick = onBackClicked) {
Icon( UpIcon(Icons.Outlined.Close.takeIf { isActionMode })
imageVector = if (isActionMode) Icons.Outlined.Close else Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
} }
}, },
actions = { actions = {

View File

@ -2,15 +2,12 @@ package eu.kanade.presentation.more.settings
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.tachiyomi.R import eu.kanade.presentation.components.UpIcon
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@Composable @Composable
@ -27,10 +24,7 @@ fun PreferenceScaffold(
navigationIcon = { navigationIcon = {
if (onBackPressed != null) { if (onBackPressed != null) {
IconButton(onClick = onBackPressed) { IconButton(onClick = onBackPressed) {
Icon( UpIcon()
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
} }
} }
}, },

View File

@ -28,6 +28,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences 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.debug.DebugInfoScreen import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R

View File

@ -281,8 +281,8 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(R.string.pref_chapter_swipe), title = stringResource(R.string.pref_chapter_swipe),
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEndAction(), pref = libraryPreferences.swipeToStartAction(),
title = stringResource(R.string.pref_chapter_swipe_end), title = stringResource(R.string.pref_chapter_swipe_start),
entries = mapOf( entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled), LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@ -291,8 +291,8 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeStartAction(), pref = libraryPreferences.swipeToEndAction(),
title = stringResource(R.string.pref_chapter_swipe_start), title = stringResource(R.string.pref_chapter_swipe_end),
entries = mapOf( entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled), LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),

View File

@ -4,11 +4,11 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
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.outlined.ArrowBack
import androidx.compose.material.icons.outlined.ChromeReaderMode import androidx.compose.material.icons.outlined.ChromeReaderMode
import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.CollectionsBookmark import androidx.compose.material.icons.outlined.CollectionsBookmark
@ -20,7 +20,6 @@ import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Security import androidx.compose.material.icons.outlined.Security
import androidx.compose.material.icons.outlined.SettingsBackupRestore import androidx.compose.material.icons.outlined.SettingsBackupRestore
import androidx.compose.material.icons.outlined.Sync import androidx.compose.material.icons.outlined.Sync
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -45,11 +44,12 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
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.UpIcon
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress import eu.kanade.presentation.util.LocalBackPress
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.LazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
@ -94,10 +94,7 @@ object SettingsMainScreen : Screen() {
}, },
navigationIcon = { navigationIcon = {
IconButton(onClick = backPress::invoke) { IconButton(onClick = backPress::invoke) {
Icon( UpIcon()
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
} }
}, },
actions = { actions = {

View File

@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen
import android.content.res.Resources
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -17,7 +16,6 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -40,19 +38,21 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
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 eu.kanade.tachiyomi.util.system.isLTR
import tachiyomi.presentation.core.components.material.Divider 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
@ -97,11 +97,7 @@ class SettingsSearchScreen : Screen() {
val canPop = remember { navigator.canPop } val canPop = remember { navigator.canPop }
if (canPop) { if (canPop) {
IconButton(onClick = navigator::pop) { IconButton(onClick = navigator::pop) {
Icon( UpIcon()
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
} }
} }
}, },
@ -169,6 +165,8 @@ private fun SearchResult(
) { ) {
if (searchKey.isEmpty()) return if (searchKey.isEmpty()) return
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val index = getIndex() val index = getIndex()
val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) { val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) {
value = index.asSequence() value = index.asSequence()
@ -204,7 +202,7 @@ private fun SearchResult(
SearchResultItem( SearchResultItem(
route = settingsData.route, route = settingsData.route,
title = p.title, title = p.title,
breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle), breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle, isLtr = isLtr),
highlightKey = p.title, highlightKey = p.title,
) )
} }
@ -269,11 +267,11 @@ private fun getIndex() = settingScreens
) )
} }
private fun getLocalizedBreadcrumb(path: String, node: String?): String { private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String {
return if (node == null) { return if (node == null) {
path path
} else { } else {
if (Resources.getSystem().isLTR) { if (isLtr) {
// This locale reads left to right. // This locale reads left to right.
"$path > $node" "$path > $node"
} else { } else {

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import android.content.Context import android.content.Context
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.advanced
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View File

@ -12,7 +12,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.AboutScreen import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil

View File

@ -4,6 +4,7 @@ import android.content.Context
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
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.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
@ -39,7 +40,6 @@ import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import tachiyomi.presentation.core.components.LazyColumn
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

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -28,7 +29,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.LazyColumn
import tachiyomi.presentation.core.components.material.Divider 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

View File

@ -3,6 +3,7 @@ package eu.kanade.presentation.more.stats
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CollectionsBookmark import androidx.compose.material.icons.outlined.CollectionsBookmark
@ -19,7 +20,6 @@ import eu.kanade.presentation.more.stats.components.StatsSection
import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.presentation.util.toDurationString import eu.kanade.presentation.util.toDurationString
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import java.util.Locale import java.util.Locale
import kotlin.time.DurationUnit import kotlin.time.DurationUnit

View File

@ -0,0 +1,158 @@
package eu.kanade.presentation.reader.settings
import android.os.Build
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.core.preference.getAndSet
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SelectItem
import tachiyomi.presentation.core.components.SliderItem
@Composable
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
val colorFilterModes = buildList {
addAll(
listOf(
R.string.label_default,
R.string.filter_mode_multiply,
R.string.filter_mode_screen,
),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
addAll(
listOf(
R.string.filter_mode_overlay,
R.string.filter_mode_lighten,
R.string.filter_mode_darken,
),
)
}
}.map { stringResource(it) }
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_custom_brightness),
checked = customBrightness,
onClick = {
screenModel.togglePreference(ReaderPreferences::customBrightness)
},
)
/**
* Sets the brightness of the screen. Range is [-75, 100].
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
* From 1 to 100 it sets that value as brightness.
* 0 sets system brightness and hides the overlay.
*/
if (customBrightness) {
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
SliderItem(
label = stringResource(R.string.pref_custom_brightness),
min = -75,
max = 100,
value = customBrightnessValue,
valueText = customBrightnessValue.toString(),
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
)
}
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_custom_color_filter),
checked = colorFilter,
onClick = {
screenModel.togglePreference(ReaderPreferences::colorFilter)
},
)
if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
SliderItem(
label = stringResource(R.string.color_filter_r_value),
max = 255,
value = colorFilterValue.red,
valueText = colorFilterValue.red.toString(),
onChange = { newRValue ->
screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, RED_MASK, 16)
}
},
)
SliderItem(
label = stringResource(R.string.color_filter_g_value),
max = 255,
value = colorFilterValue.green,
valueText = colorFilterValue.green.toString(),
onChange = { newGValue ->
screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newGValue, GREEN_MASK, 8)
}
},
)
SliderItem(
label = stringResource(R.string.color_filter_b_value),
max = 255,
value = colorFilterValue.blue,
valueText = colorFilterValue.blue.toString(),
onChange = { newBValue ->
screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newBValue, BLUE_MASK, 0)
}
},
)
SliderItem(
label = stringResource(R.string.color_filter_a_value),
max = 255,
value = colorFilterValue.alpha,
valueText = colorFilterValue.alpha.toString(),
onChange = { newAValue ->
screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newAValue, ALPHA_MASK, 24)
}
},
)
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
SelectItem(
label = stringResource(R.string.pref_color_filter_mode),
options = colorFilterModes.toTypedArray(),
selectedIndex = colorFilterMode,
) {
screenModel.preferences.colorFilterMode().set(it)
}
}
val grayscale by screenModel.preferences.grayscale().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_grayscale),
checked = grayscale,
onClick = {
screenModel.togglePreference(ReaderPreferences::grayscale)
},
)
val invertedColors by screenModel.preferences.invertedColors().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_inverted_colors),
checked = invertedColors,
onClick = {
screenModel.togglePreference(ReaderPreferences::invertedColors)
},
)
}
private fun getColorValue(currentColor: Int, color: Int, mask: Long, bitShift: Int): Int {
return (color shl bitShift) or (currentColor and mask.inv().toInt())
}
private const val ALPHA_MASK: Long = 0xFF000000
private const val RED_MASK: Long = 0x00FF0000
private const val GREEN_MASK: Long = 0x0000FF00
private const val BLUE_MASK: Long = 0x000000FF

View File

@ -0,0 +1,96 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.RadioItem
@Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
// TODO: show this in a nicer way
HeadingItem(stringResource(R.string.pref_reader_theme))
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
listOf(
R.string.black_background to 1,
R.string.gray_background to 2,
R.string.white_background to 0,
R.string.automatic_background to 3,
).map { (titleRes, theme) ->
RadioItem(
label = stringResource(titleRes),
selected = readerTheme == theme,
onClick = { screenModel.preferences.readerTheme().set(theme) },
)
}
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_show_page_number),
checked = showPageNumber,
onClick = {
screenModel.togglePreference(ReaderPreferences::showPageNumber)
},
)
val fullscreen by screenModel.preferences.fullscreen().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_fullscreen),
checked = fullscreen,
onClick = {
screenModel.togglePreference(ReaderPreferences::fullscreen)
},
)
// TODO: hide if there's no cutout
val cutoutShort by screenModel.preferences.cutoutShort().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_cutout_short),
checked = cutoutShort,
onClick = {
screenModel.togglePreference(ReaderPreferences::cutoutShort)
},
)
val keepScreenOn by screenModel.preferences.keepScreenOn().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_keep_screen_on),
checked = keepScreenOn,
onClick = {
screenModel.togglePreference(ReaderPreferences::keepScreenOn)
},
)
val readWithLongTap by screenModel.preferences.readWithLongTap().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_read_with_long_tap),
checked = readWithLongTap,
onClick = {
screenModel.togglePreference(ReaderPreferences::readWithLongTap)
},
)
val alwaysShowChapterTransition by screenModel.preferences.alwaysShowChapterTransition().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_always_show_chapter_transition),
checked = alwaysShowChapterTransition,
onClick = {
screenModel.togglePreference(ReaderPreferences::alwaysShowChapterTransition)
},
)
val pageTransitions by screenModel.preferences.pageTransitions().collectAsState()
CheckboxItem(
label = stringResource(R.string.pref_page_transitions),
checked = pageTransitions,
onClick = {
screenModel.togglePreference(ReaderPreferences::pageTransitions)
},
)
}

View File

@ -0,0 +1,66 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogWindowProvider
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
@Composable
fun ReaderSettingsDialog(
onDismissRequest: () -> Unit,
onShowMenus: () -> Unit,
onHideMenus: () -> Unit,
screenModel: ReaderSettingsScreenModel,
) {
// TODO: undimming doesn't seem to work
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
val tabTitles = listOf(
stringResource(R.string.pref_category_reading_mode),
stringResource(R.string.pref_category_general),
stringResource(R.string.custom_filter),
)
val pagerState = rememberPagerState { tabTitles.size }
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage == 2) {
window?.setDimAmount(0f)
onHideMenus()
} else {
window?.setDimAmount(0.75f)
onShowMenus()
}
}
TabbedDialog(
onDismissRequest = {
onDismissRequest()
onShowMenus()
},
tabTitles = tabTitles,
pagerState = pagerState,
) { page ->
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
when (page) {
0 -> ReadingModePage(screenModel)
1 -> GeneralPage(screenModel)
2 -> ColorFilterPage(screenModel)
}
}
}
}

View File

@ -0,0 +1,10 @@
package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
@Composable
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
// TODO
}

View File

@ -28,7 +28,6 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.crash.CrashActivity import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer import eu.kanade.tachiyomi.data.coil.MangaKeyer
@ -54,7 +53,6 @@ import org.acra.ktx.initAcra
import org.acra.sender.HttpSender import org.acra.sender.HttpSender
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.widget.TachiyomiWidgetManager import tachiyomi.presentation.widget.TachiyomiWidgetManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -64,11 +62,9 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val basePreferences: BasePreferences by injectLazy() private val basePreferences: BasePreferences by injectLazy()
private val libraryPreferences: LibraryPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy() private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver() private val disableIncognitoReceiver = DisableIncognitoReceiver()
private val chapterCache: ChapterCache by injectLazy()
@SuppressLint("LaunchActivityFromNotification") @SuppressLint("LaunchActivityFromNotification")
override fun onCreate() { override fun onCreate() {
@ -172,10 +168,6 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
SecureActivityDelegate.onApplicationStopped() SecureActivityDelegate.onApplicationStopped()
if (libraryPreferences.autoClearChapterCache().get()) {
chapterCache.clear()
}
} }
override fun getPackageName(): String { override fun getPackageName(): String {

View File

@ -21,12 +21,11 @@ import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.workManager 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.getEnum import tachiyomi.core.preference.getEnum
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
import tachiyomi.domain.manga.model.TriStateFilter
import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
object Migrations { object Migrations {
@ -350,12 +349,12 @@ object Migrations {
remove(key) remove(key)
val newValue = when (pref.get()) { val newValue = when (pref.get()) {
1 -> TriStateFilter.ENABLED_IS 1 -> TriState.ENABLED_IS
2 -> TriStateFilter.ENABLED_NOT 2 -> TriState.ENABLED_NOT
else -> TriStateFilter.DISABLED else -> TriState.DISABLED
} }
preferenceStore.getEnum("${key}_v2", TriStateFilter.DISABLED).set(newValue) preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
} }
} }
} }

View File

@ -20,7 +20,7 @@ data class BackupChapter(
// chapterNumber is called number is 1.x // chapterNumber is called number is 1.x
@ProtoNumber(9) var chapterNumber: Float = 0F, @ProtoNumber(9) var chapterNumber: Float = 0F,
@ProtoNumber(10) var sourceOrder: Long = 0, @ProtoNumber(10) var sourceOrder: Long = 0,
@ProtoNumber(11) var lastModifiedAt: Long? = null, @ProtoNumber(11) var lastModifiedAt: Long = 0,
) { ) {
fun toChapterImpl(): Chapter { fun toChapterImpl(): Chapter {
return Chapter.create().copy( return Chapter.create().copy(
@ -39,7 +39,7 @@ 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: Float, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long ->
BackupChapter( BackupChapter(
url = url, url = url,
name = name, name = name,

View File

@ -39,7 +39,8 @@ data class BackupManga(
@ProtoNumber(103) var viewer_flags: Int? = null, @ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(), @ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
@ProtoNumber(106) var lastModifiedAt: Long? = 0, @ProtoNumber(106) var lastModifiedAt: Long = 0,
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
) { ) {
fun getMangaImpl(): Manga { fun getMangaImpl(): Manga {
return Manga.create().copy( return Manga.create().copy(
@ -58,6 +59,7 @@ data class BackupManga(
chapterFlags = this@BackupManga.chapterFlags.toLong(), chapterFlags = this@BackupManga.chapterFlags.toLong(),
updateStrategy = this@BackupManga.updateStrategy, updateStrategy = this@BackupManga.updateStrategy,
lastModifiedAt = this@BackupManga.lastModifiedAt, lastModifiedAt = this@BackupManga.lastModifiedAt,
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
) )
} }
@ -92,6 +94,7 @@ data class BackupManga(
chapterFlags = manga.chapterFlags.toInt(), chapterFlags = manga.chapterFlags.toInt(),
updateStrategy = manga.updateStrategy, updateStrategy = manga.updateStrategy,
lastModifiedAt = manga.lastModifiedAt, lastModifiedAt = manga.lastModifiedAt,
favoriteModifiedAt = manga.favoriteModifiedAt,
) )
} }
} }

View File

@ -20,7 +20,7 @@ interface Chapter : SChapter, Serializable {
var source_order: Int var source_order: Int
var last_modified: Long? var last_modified: Long
} }
fun Chapter.toDomainChapter(): DomainChapter? { fun Chapter.toDomainChapter(): DomainChapter? {

View File

@ -26,7 +26,7 @@ class ChapterImpl : Chapter {
override var source_order: Int = 0 override var source_order: Int = 0
override var last_modified: Long? = null override var last_modified: Long = 0
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -7,9 +7,6 @@ import android.view.View
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@ -21,7 +18,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -38,6 +34,7 @@ import androidx.preference.forEach
import androidx.preference.getOnBindEditTextListener import androidx.preference.getOnBindEditTextListener
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
@ -62,10 +59,7 @@ class SourcePreferencesScreen(val sourceId: Long) : Screen() {
title = { Text(text = Injekt.get<SourceManager>().getOrStub(sourceId).toString()) }, title = { Text(text = Injekt.get<SourceManager>().getOrStub(sourceId).toString()) },
navigationIcon = { navigationIcon = {
IconButton(onClick = navigator::pop) { IconButton(onClick = navigator::pop) {
Icon( UpIcon()
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
} }
}, },
scrollBehavior = it, scrollBehavior = it,

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration
import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
@ -12,15 +13,18 @@ import uy.kohesive.injekt.injectLazy
object MigrationFlags { object MigrationFlags {
private const val CHAPTERS = 0b0001 private const val CHAPTERS = 0b00001
private const val CATEGORIES = 0b0010 private const val CATEGORIES = 0b00010
private const val TRACK = 0b0100 private const val TRACK = 0b00100
private const val CUSTOM_COVER = 0b1000 private const val CUSTOM_COVER = 0b01000
private const val DELETE_DOWNLOADED = 0b10000
private val coverCache: CoverCache by injectLazy() private val coverCache: CoverCache by injectLazy()
private val getTracks: GetTracks = Injekt.get() private val getTracks: GetTracks = Injekt.get()
private val downloadCache: DownloadCache by injectLazy()
val flags get() = arrayOf(CHAPTERS, CATEGORIES, TRACK, CUSTOM_COVER) val flags get() = arrayOf(CHAPTERS, CATEGORIES, TRACK, CUSTOM_COVER, DELETE_DOWNLOADED)
private var enableFlags = emptyList<Int>().toMutableList()
fun hasChapters(value: Int): Boolean { fun hasChapters(value: Int): Boolean {
return value and CHAPTERS != 0 return value and CHAPTERS != 0
@ -38,23 +42,36 @@ object MigrationFlags {
return value and CUSTOM_COVER != 0 return value and CUSTOM_COVER != 0
} }
fun hasDeleteDownloaded(value: Int): Boolean {
return value and DELETE_DOWNLOADED != 0
}
fun getEnabledFlagsPositions(value: Int): List<Int> { fun getEnabledFlagsPositions(value: Int): List<Int> {
return flags.mapIndexedNotNull { index, flag -> if (value and flag != 0) index else null } return flags.mapIndexedNotNull { index, flag -> if (value and flag != 0) index else null }
} }
fun getFlagsFromPositions(positions: Array<Int>): Int { fun getFlagsFromPositions(positions: Array<Int>): Int {
return positions.fold(0) { accumulated, position -> accumulated or (1 shl position) } val fold = positions.fold(0) { accumulated, position -> accumulated or enableFlags[position] }
enableFlags.clear()
return fold
} }
fun titles(manga: Manga?): Array<Int> { fun titles(manga: Manga?): Array<Int> {
enableFlags.add(CHAPTERS)
enableFlags.add(CATEGORIES)
val titles = arrayOf(R.string.chapters, R.string.categories).toMutableList() val titles = arrayOf(R.string.chapters, R.string.categories).toMutableList()
if (manga != null) { if (manga != null) {
if (runBlocking { getTracks.await(manga.id) }.isNotEmpty()) { if (runBlocking { getTracks.await(manga.id) }.isNotEmpty()) {
titles.add(R.string.track) titles.add(R.string.track)
enableFlags.add(TRACK)
} }
if (manga.hasCustomCover(coverCache)) { if (manga.hasCustomCover(coverCache)) {
titles.add(R.string.custom_cover) titles.add(R.string.custom_cover)
enableFlags.add(CUSTOM_COVER)
}
if (downloadCache.getDownloadCount(manga) > 0) {
titles.add(R.string.delete_downloaded)
enableFlags.add(DELETE_DOWNLOADED)
} }
} }
return titles.toTypedArray() return titles.toTypedArray()

View File

@ -34,6 +34,7 @@ import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
@ -161,6 +162,7 @@ internal fun MigrateDialog(
internal class MigrateDialogScreenModel( internal class MigrateDialogScreenModel(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
@ -219,6 +221,7 @@ internal class MigrateDialogScreenModel(
val migrateCategories = MigrationFlags.hasCategories(flags) val migrateCategories = MigrationFlags.hasCategories(flags)
val migrateTracks = MigrationFlags.hasTracks(flags) val migrateTracks = MigrationFlags.hasTracks(flags)
val migrateCustomCover = MigrationFlags.hasCustomCover(flags) val migrateCustomCover = MigrationFlags.hasCustomCover(flags)
val deleteDownloaded = MigrationFlags.hasDeleteDownloaded(flags)
try { try {
syncChaptersWithSource.await(sourceChapters, newManga, newSource) syncChaptersWithSource.await(sourceChapters, newManga, newSource)
@ -283,6 +286,13 @@ internal class MigrateDialogScreenModel(
insertTrack.awaitAll(tracks) insertTrack.awaitAll(tracks)
} }
// Delete downloaded
if (deleteDownloaded) {
if (oldSource != null) {
downloadManager.deleteManga(oldManga, oldSource)
}
}
if (replace) { if (replace) {
updateManga.await(MangaUpdate(oldManga.id, favorite = false, dateAdded = 0)) updateManga.await(MangaUpdate(oldManga.id, favorite = false, dateAdded = 0))
} }

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -15,18 +16,17 @@ import androidx.compose.ui.Modifier
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.presentation.components.AdaptiveSheet import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.components.SelectItem
import eu.kanade.presentation.components.TriStateItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.core.preference.TriState
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.CollapsibleBox import tachiyomi.presentation.core.components.CollapsibleBox
import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.SelectItem
import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TextItem import tachiyomi.presentation.core.components.TextItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.Button import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Divider
@ -164,19 +164,19 @@ private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit) {
} }
} }
private fun Int.toTriStateFilter(): TriStateFilter { private fun Int.toTriStateFilter(): TriState {
return when (this) { return when (this) {
Filter.TriState.STATE_IGNORE -> TriStateFilter.DISABLED Filter.TriState.STATE_IGNORE -> TriState.DISABLED
Filter.TriState.STATE_INCLUDE -> TriStateFilter.ENABLED_IS Filter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS
Filter.TriState.STATE_EXCLUDE -> TriStateFilter.ENABLED_NOT Filter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT
else -> throw IllegalStateException("Unknown TriState state: $this") else -> throw IllegalStateException("Unknown TriState state: $this")
} }
} }
private fun TriStateFilter.toTriStateInt(): Int { private fun TriState.toTriStateInt(): Int {
return when (this) { return when (this) {
TriStateFilter.DISABLED -> Filter.TriState.STATE_IGNORE TriState.DISABLED -> Filter.TriState.STATE_IGNORE
TriStateFilter.ENABLED_IS -> Filter.TriState.STATE_INCLUDE TriState.ENABLED_IS -> Filter.TriState.STATE_INCLUDE
TriStateFilter.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE TriState.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE
} }
} }

View File

@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.TriState
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
@ -57,7 +58,6 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetLibraryManga import tachiyomi.domain.manga.interactor.GetLibraryManga
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.model.TriStateFilter
import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracksPerManga import tachiyomi.domain.track.interactor.GetTracksPerManga
@ -153,7 +153,7 @@ class LibraryScreenModel(
prefs.filterBookmarked, prefs.filterBookmarked,
prefs.filterCompleted, prefs.filterCompleted,
) + trackFilter.values ) + trackFilter.values
).any { it != TriStateFilter.DISABLED } ).any { it != TriState.DISABLED }
} }
.distinctUntilChanged() .distinctUntilChanged()
.onEach { .onEach {
@ -169,12 +169,12 @@ class LibraryScreenModel(
*/ */
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Long>>, trackMap: Map<Long, List<Long>>,
loggedInTrackServices: Map<Long, TriStateFilter>, loggedInTrackServices: Map<Long, TriState>,
): LibraryMap { ): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first() val prefs = getLibraryItemPreferencesFlow().first()
val downloadedOnly = prefs.globalFilterDownloaded val downloadedOnly = prefs.globalFilterDownloaded
val filterDownloaded = val filterDownloaded =
if (downloadedOnly) TriStateFilter.ENABLED_IS else prefs.filterDownloaded if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded
val filterUnread = prefs.filterUnread val filterUnread = prefs.filterUnread
val filterStarted = prefs.filterStarted val filterStarted = prefs.filterStarted
val filterBookmarked = prefs.filterBookmarked val filterBookmarked = prefs.filterBookmarked
@ -182,8 +182,8 @@ class LibraryScreenModel(
val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty() val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_NOT) it.key else null } val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_IS) it.key else null } val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
val filterFnDownloaded: (LibraryItem) -> Boolean = { val filterFnDownloaded: (LibraryItem) -> Boolean = {
@ -308,11 +308,11 @@ class LibraryScreenModel(
localBadge = it[1] as Boolean, localBadge = it[1] as Boolean,
languageBadge = it[2] as Boolean, languageBadge = it[2] as Boolean,
globalFilterDownloaded = it[3] as Boolean, globalFilterDownloaded = it[3] as Boolean,
filterDownloaded = it[4] as TriStateFilter, filterDownloaded = it[4] as TriState,
filterUnread = it[5] as TriStateFilter, filterUnread = it[5] as TriState,
filterStarted = it[6] as TriStateFilter, filterStarted = it[6] as TriState,
filterBookmarked = it[7] as TriStateFilter, filterBookmarked = it[7] as TriState,
filterCompleted = it[8] as TriStateFilter, filterCompleted = it[8] as TriState,
) )
}, },
) )
@ -365,7 +365,7 @@ class LibraryScreenModel(
* *
* @return map of track id with the filter value * @return map of track id with the filter value
*/ */
private fun getTrackingFilterFlow(): Flow<Map<Long, TriStateFilter>> { private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged }
return if (loggedServices.isNotEmpty()) { return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices val prefFlows = loggedServices
@ -670,11 +670,11 @@ class LibraryScreenModel(
val languageBadge: Boolean, val languageBadge: Boolean,
val globalFilterDownloaded: Boolean, val globalFilterDownloaded: Boolean,
val filterDownloaded: TriStateFilter, val filterDownloaded: TriState,
val filterUnread: TriStateFilter, val filterUnread: TriState,
val filterStarted: TriStateFilter, val filterStarted: TriState,
val filterBookmarked: TriStateFilter, val filterBookmarked: TriState,
val filterCompleted: TriStateFilter, val filterCompleted: TriState,
) )
@Immutable @Immutable

View File

@ -6,6 +6,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.util.preference.toggle import eu.kanade.tachiyomi.util.preference.toggle
import tachiyomi.core.preference.Preference import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getAndSet import tachiyomi.core.preference.getAndSet
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.domain.category.interactor.SetDisplayMode import tachiyomi.domain.category.interactor.SetDisplayMode
@ -14,7 +15,6 @@ import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.TriStateFilter
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -33,7 +33,7 @@ class LibrarySettingsScreenModel(
preference(libraryPreferences).toggle() preference(libraryPreferences).toggle()
} }
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriStateFilter>) { fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
preference(libraryPreferences).getAndSet { preference(libraryPreferences).getAndSet {
it.next() it.next()
} }

View File

@ -63,6 +63,7 @@ import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
@ -105,6 +106,7 @@ class MainActivity : BaseActivity() {
private val preferences: BasePreferences by injectLazy() private val preferences: BasePreferences by injectLazy()
private val downloadCache: DownloadCache by injectLazy() private val downloadCache: DownloadCache by injectLazy()
private val chapterCache: ChapterCache by injectLazy()
// To be checked by splash screen. If true then splash screen will be removed. // To be checked by splash screen. If true then splash screen will be removed.
var ready = false var ready = false
@ -112,12 +114,14 @@ class MainActivity : BaseActivity() {
private var navigator: Navigator? = null private var navigator: Navigator? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val isLaunch = savedInstanceState == null
// Prevent splash screen showing up on configuration changes // Prevent splash screen showing up on configuration changes
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null val splashScreen = if (isLaunch) installSplashScreen() else null
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val didMigration = if (savedInstanceState == null) { val didMigration = if (isLaunch) {
Migrations.upgrade( Migrations.upgrade(
context = applicationContext, context = applicationContext,
basePreferences = preferences, basePreferences = preferences,
@ -149,7 +153,7 @@ class MainActivity : BaseActivity() {
val downloadOnly by preferences.downloadedOnly().collectAsState() val downloadOnly by preferences.downloadedOnly().collectAsState()
val indexing by downloadCache.isInitializing.collectAsState() val indexing by downloadCache.isInitializing.collectAsState()
// Set statusbar color considering the top app state banner // Set status bar color considering the top app state banner
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val isSystemInDarkTheme = isSystemInDarkTheme() val isSystemInDarkTheme = isSystemInDarkTheme()
val statusBarBackgroundColor = when { val statusBarBackgroundColor = when {
@ -189,7 +193,7 @@ class MainActivity : BaseActivity() {
LaunchedEffect(navigator) { LaunchedEffect(navigator) {
this@MainActivity.navigator = navigator this@MainActivity.navigator = navigator
if (savedInstanceState == null) { if (isLaunch) {
// Set start screen // Set start screen
handleIntentAction(intent, navigator) handleIntentAction(intent, navigator)
@ -267,6 +271,10 @@ class MainActivity : BaseActivity() {
elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION) elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
} }
setSplashScreenExitAnimation(splashScreen) setSplashScreenExitAnimation(splashScreen)
if (isLaunch && libraryPreferences.autoClearChapterCache().get()) {
chapterCache.clear()
}
} }
override fun onProvideAssistContent(outContent: AssistContent) { override fun onProvideAssistContent(outContent: AssistContent) {
@ -279,7 +287,7 @@ class MainActivity : BaseActivity() {
} }
@Composable @Composable
fun HandleOnNewIntent(context: Context, navigator: Navigator) { private fun HandleOnNewIntent(context: Context, navigator: Navigator) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
callbackFlow<Intent> { callbackFlow<Intent> {
val componentActivity = context as ComponentActivity val componentActivity = context as ComponentActivity

View File

@ -101,8 +101,8 @@ class MangaScreen(
dateRelativeTime = screenModel.relativeTime, dateRelativeTime = screenModel.relativeTime,
dateFormat = screenModel.dateFormat, dateFormat = screenModel.dateFormat,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
chapterSwipeStartAction = screenModel.chapterSwipeStartAction, chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
onBackClicked = navigator::pop, onBackClicked = navigator::pop,
onChapterClicked = { openChapter(context, it) }, onChapterClicked = { openChapter(context, it) },
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() }, onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },

View File

@ -46,6 +46,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.preference.mapAsCheckboxState
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
@ -67,7 +68,6 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetMangaWithChapters import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
@ -119,11 +119,11 @@ class MangaInfoScreenModel(
private val allChapters: List<ChapterItem>? private val allChapters: List<ChapterItem>?
get() = successState?.chapters get() = successState?.chapters
private val filteredChapters: Sequence<ChapterItem>? private val filteredChapters: List<ChapterItem>?
get() = successState?.processedChapters get() = successState?.processedChapters
val chapterSwipeEndAction = libraryPreferences.swipeEndAction().get() val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
val chapterSwipeStartAction = libraryPreferences.swipeStartAction().get() val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) val relativeTime by uiPreferences.relativeTime().asState(coroutineScope)
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
@ -576,7 +576,7 @@ class MangaInfoScreenModel(
} }
private fun getUnreadChapters(): List<Chapter> { private fun getUnreadChapters(): List<Chapter> {
val chapterItems = if (skipFiltered) filteredChapters.orEmpty().toList() else allChapters.orEmpty() val chapterItems = if (skipFiltered) filteredChapters.orEmpty() else allChapters.orEmpty()
return chapterItems return chapterItems
.filter { (chapter, dlStatus) -> !chapter.read && dlStatus == Download.State.NOT_DOWNLOADED } .filter { (chapter, dlStatus) -> !chapter.read && dlStatus == Download.State.NOT_DOWNLOADED }
.map { it.chapter } .map { it.chapter }
@ -664,7 +664,7 @@ class MangaInfoScreenModel(
fun markPreviousChapterRead(pointer: Chapter) { fun markPreviousChapterRead(pointer: Chapter) {
val successState = successState ?: return val successState = successState ?: return
val chapters = filteredChapters.orEmpty().map { it.chapter }.toList() val chapters = filteredChapters.orEmpty().map { it.chapter }
val prevChapters = if (successState.manga.sortDescending()) chapters.asReversed() else chapters val prevChapters = if (successState.manga.sortDescending()) chapters.asReversed() else chapters
val pointerPos = prevChapters.indexOf(pointer) val pointerPos = prevChapters.indexOf(pointer)
if (pointerPos != -1) markChaptersRead(prevChapters.take(pointerPos), true) if (pointerPos != -1) markChaptersRead(prevChapters.take(pointerPos), true)
@ -743,13 +743,13 @@ class MangaInfoScreenModel(
* Sets the read filter and requests an UI update. * Sets the read filter and requests an UI update.
* @param state whether to display only unread chapters or all chapters. * @param state whether to display only unread chapters or all chapters.
*/ */
fun setUnreadFilter(state: TriStateFilter) { fun setUnreadFilter(state: TriState) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
TriStateFilter.DISABLED -> Manga.SHOW_ALL TriState.DISABLED -> Manga.SHOW_ALL
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ
} }
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
setMangaChapterFlags.awaitSetUnreadFilter(manga, flag) setMangaChapterFlags.awaitSetUnreadFilter(manga, flag)
@ -760,13 +760,13 @@ class MangaInfoScreenModel(
* Sets the download filter and requests an UI update. * Sets the download filter and requests an UI update.
* @param state whether to display only downloaded chapters or all chapters. * @param state whether to display only downloaded chapters or all chapters.
*/ */
fun setDownloadedFilter(state: TriStateFilter) { fun setDownloadedFilter(state: TriState) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
TriStateFilter.DISABLED -> Manga.SHOW_ALL TriState.DISABLED -> Manga.SHOW_ALL
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
} }
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
@ -778,13 +778,13 @@ class MangaInfoScreenModel(
* Sets the bookmark filter and requests an UI update. * Sets the bookmark filter and requests an UI update.
* @param state whether to display only bookmarked chapters or all chapters. * @param state whether to display only bookmarked chapters or all chapters.
*/ */
fun setBookmarkedFilter(state: TriStateFilter) { fun setBookmarkedFilter(state: TriState) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
TriStateFilter.DISABLED -> Manga.SHOW_ALL TriState.DISABLED -> Manga.SHOW_ALL
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
} }
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
@ -987,8 +987,9 @@ sealed class MangaScreenState {
val hasPromptedToAddBefore: Boolean = false, val hasPromptedToAddBefore: Boolean = false,
) : MangaScreenState() { ) : MangaScreenState() {
val processedChapters: Sequence<ChapterItem> val processedChapters by lazy {
get() = chapters.applyFilters(manga) chapters.applyFilters(manga).toList()
}
val trackingAvailable: Boolean val trackingAvailable: Boolean
get() = trackItems.isNotEmpty() get() = trackItems.isNotEmpty()

View File

@ -31,6 +31,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -51,6 +52,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.orientationType import eu.kanade.domain.manga.model.orientationType
import eu.kanade.presentation.reader.ChapterNavigator import eu.kanade.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.PageIndicatorText import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
@ -65,8 +67,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderColorFilterDialog
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
@ -235,13 +237,18 @@ class ReaderActivity : BaseActivity() {
readingModeToast?.cancel() readingModeToast?.cancel()
} }
override fun onPause() {
viewModel.flushReadTimer()
super.onPause()
}
/** /**
* Set menu visibility again on activity resume to apply immersive mode again if needed. * Set menu visibility again on activity resume to apply immersive mode again if needed.
* Helps with rotations. * Helps with rotations.
*/ */
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.setReadStartTime() viewModel.restartReadTimer()
setMenuVisibility(viewModel.state.value.menuVisible, animate = false) setMenuVisibility(viewModel.state.value.menuVisible, animate = false)
} }
@ -384,6 +391,8 @@ class ReaderActivity : BaseActivity() {
binding.dialogRoot.setComposeContent { binding.dialogRoot.setComposeContent {
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val settingsScreenModel = remember { ReaderSettingsScreenModel() }
val onDismissRequest = viewModel::closeDialog val onDismissRequest = viewModel::closeDialog
when (state.dialog) { when (state.dialog) {
is ReaderViewModel.Dialog.Loading -> { is ReaderViewModel.Dialog.Loading -> {
@ -401,14 +410,12 @@ class ReaderActivity : BaseActivity() {
}, },
) )
} }
is ReaderViewModel.Dialog.ColorFilter -> { is ReaderViewModel.Dialog.Settings -> {
setMenuVisibility(false) ReaderSettingsDialog(
ReaderColorFilterDialog( onDismissRequest = onDismissRequest,
onDismissRequest = { onShowMenus = { setMenuVisibility(true) },
onDismissRequest() onHideMenus = { setMenuVisibility(false) },
setMenuVisibility(true) screenModel = settingsScreenModel,
},
readerPreferences = viewModel.readerPreferences,
) )
} }
is ReaderViewModel.Dialog.PageActions -> { is ReaderViewModel.Dialog.PageActions -> {
@ -541,7 +548,7 @@ class ReaderActivity : BaseActivity() {
} }
// Settings sheet // Settings sheet
with(binding.actionSettings) { with(binding.actionSettingsLegacy) {
setTooltip(R.string.action_settings) setTooltip(R.string.action_settings)
var readerSettingSheet: ReaderSettingsSheet? = null var readerSettingSheet: ReaderSettingsSheet? = null
@ -551,13 +558,11 @@ class ReaderActivity : BaseActivity() {
readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() } readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() }
} }
} }
with(binding.actionSettings) {
// Color filter sheet setTooltip(R.string.action_settings)
with(binding.actionColorSettings) {
setTooltip(R.string.custom_filter)
setOnClickListener { setOnClickListener {
viewModel.openColorFilterDialog() viewModel.openSettingsDialog()
} }
} }
} }
@ -588,7 +593,7 @@ class ReaderActivity : BaseActivity() {
* Sets the visibility of the menu according to [visible] and with an optional parameter to * Sets the visibility of the menu according to [visible] and with an optional parameter to
* [animate] the views. * [animate] the views.
*/ */
fun setMenuVisibility(visible: Boolean, animate: Boolean = true) { private fun setMenuVisibility(visible: Boolean, animate: Boolean = true) {
viewModel.showMenus(visible) viewModel.showMenus(visible)
if (visible) { if (visible) {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
@ -793,7 +798,6 @@ class ReaderActivity : BaseActivity() {
* Called from the viewer whenever a [page] is marked as active. It updates the values of the * Called from the viewer whenever a [page] is marked as active. It updates the values of the
* bottom menu and delegates the change to the presenter. * bottom menu and delegates the change to the presenter.
*/ */
@SuppressLint("SetTextI18n")
fun onPageSelected(page: ReaderPage) { fun onPageSelected(page: ReaderPage) {
viewModel.onPageSelected(page) viewModel.onPageSelected(page)
} }
@ -811,7 +815,7 @@ class ReaderActivity : BaseActivity() {
* the viewer is reaching the beginning or end of a chapter or the transition page is active. * the viewer is reaching the beginning or end of a chapter or the transition page is active.
*/ */
fun requestPreloadChapter(chapter: ReaderChapter) { fun requestPreloadChapter(chapter: ReaderChapter) {
lifecycleScope.launchIO { viewModel.preloadChapter(chapter) } lifecycleScope.launchIO { viewModel.preload(chapter) }
} }
/** /**
@ -898,7 +902,7 @@ class ReaderActivity : BaseActivity() {
/** /**
* Updates viewer inset depending on fullscreen reader preferences. * Updates viewer inset depending on fullscreen reader preferences.
*/ */
fun updateViewerInset(fullscreen: Boolean) { private fun updateViewerInset(fullscreen: Boolean) {
viewModel.state.value.viewer?.getView()?.applyInsetter { viewModel.state.value.viewer?.getView()?.applyInsetter {
if (!fullscreen) { if (!fullscreen) {
type(navigationBars = true, statusBars = true) { type(navigationBars = true, statusBars = true) {

View File

@ -10,10 +10,8 @@ import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
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.domain.track.model.toDbTrack import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.track.store.DelayedTrackingStore
import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
@ -21,10 +19,10 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.saver.Image import eu.kanade.tachiyomi.data.saver.Image
import eu.kanade.tachiyomi.data.saver.ImageSaver import eu.kanade.tachiyomi.data.saver.ImageSaver
import eu.kanade.tachiyomi.data.saver.Location import eu.kanade.tachiyomi.data.saver.Location
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@ -41,11 +39,8 @@ import eu.kanade.tachiyomi.util.lang.byteSize
import eu.kanade.tachiyomi.util.lang.takeBytes import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.system.isOnline
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -56,7 +51,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
@ -75,8 +69,6 @@ import tachiyomi.domain.history.model.HistoryUpdate
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.source.local.isLocal 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
@ -96,12 +88,10 @@ class ReaderViewModel(
private val basePreferences: BasePreferences = Injekt.get(), private val basePreferences: BasePreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val trackPreferences: TrackPreferences = Injekt.get(), private val trackPreferences: TrackPreferences = Injekt.get(),
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(), private val trackChapter: TrackChapter = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getNextChapters: GetNextChapters = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(), private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(), private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
@ -197,13 +187,8 @@ class ReaderViewModel(
.map(::ReaderChapter) .map(::ReaderChapter)
} }
private var hasTrackers: Boolean = false
private val checkTrackers: (Manga) -> Unit = { manga ->
val tracks = runBlocking { getTracks.await(manga.id) }
hasTrackers = tracks.isNotEmpty()
}
private val incognitoMode = preferences.incognitoMode().get() private val incognitoMode = preferences.incognitoMode().get()
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
init { init {
// To save state // To save state
@ -257,8 +242,6 @@ class ReaderViewModel(
mutableState.update { it.copy(manga = manga) } mutableState.update { it.copy(manga = manga) }
if (chapterId == -1L) chapterId = initialChapterId if (chapterId == -1L) chapterId = initialChapterId
checkTrackers(manga)
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(manga.source) val source = sourceManager.getOrStub(manga.source)
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source) loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
@ -312,12 +295,15 @@ class ReaderViewModel(
* Called when the user changed to the given [chapter] when changing pages from the viewer. * Called when the user changed to the given [chapter] when changing pages from the viewer.
* It's used only to set this chapter as active. * It's used only to set this chapter as active.
*/ */
private suspend fun loadNewChapter(chapter: ReaderChapter) { private fun loadNewChapter(chapter: ReaderChapter) {
val loader = loader ?: return val loader = loader ?: return
logcat { "Loading ${chapter.chapter.url}" } viewModelScope.launchIO {
logcat { "Loading ${chapter.chapter.url}" }
flushReadTimer()
restartReadTimer()
withIOContext {
try { try {
loadChapter(loader, chapter) loadChapter(loader, chapter)
} catch (e: Throwable) { } catch (e: Throwable) {
@ -356,7 +342,7 @@ class ReaderViewModel(
* Called when the viewers decide it's a good time to preload a [chapter] and improve the UX so * Called when the viewers decide it's a good time to preload a [chapter] and improve the UX so
* that the user doesn't have to wait too long to continue reading. * that the user doesn't have to wait too long to continue reading.
*/ */
private suspend fun preload(chapter: ReaderChapter) { suspend fun preload(chapter: ReaderChapter) {
if (chapter.state is ReaderChapter.State.Loaded || chapter.state == ReaderChapter.State.Loading) { if (chapter.state is ReaderChapter.State.Loaded || chapter.state == ReaderChapter.State.Loading) {
return return
} }
@ -395,9 +381,7 @@ class ReaderViewModel(
fun onViewerLoaded(viewer: Viewer?) { fun onViewerLoaded(viewer: Viewer?) {
mutableState.update { mutableState.update {
it.copy( it.copy(viewer = viewer)
viewer = viewer,
)
} }
} }
@ -412,31 +396,19 @@ class ReaderViewModel(
return return
} }
val currentChapters = state.value.viewerChapters ?: return
val pages = page.chapter.pages ?: return
val selectedChapter = page.chapter val selectedChapter = page.chapter
val pages = selectedChapter.pages ?: return
// Save last page read and mark as read if needed // Save last page read and mark as read if needed
saveReadingProgress() viewModelScope.launchNonCancellable {
mutableState.update { updateChapterProgress(selectedChapter, page.index)
it.copy(
currentPage = page.index + 1,
)
}
if (!incognitoMode) {
selectedChapter.chapter.last_page_read = page.index
if (selectedChapter.pages?.lastIndex == page.index) {
selectedChapter.chapter.read = true
updateTrackChapterRead(selectedChapter)
deleteChapterIfNeeded(selectedChapter)
}
} }
if (selectedChapter != currentChapters.currChapter) { if (selectedChapter != getCurrentChapter()) {
logcat { "Setting ${selectedChapter.chapter.url} as active" } logcat { "Setting ${selectedChapter.chapter.url} as active" }
setReadStartTime() loadNewChapter(selectedChapter)
viewModelScope.launch { loadNewChapter(selectedChapter) }
} }
val inDownloadRange = page.number.toDouble() / pages.size > 0.25 val inDownloadRange = page.number.toDouble() / pages.size > 0.25
if (inDownloadRange) { if (inDownloadRange) {
downloadNextChapters() downloadNextChapters()
@ -444,12 +416,11 @@ class ReaderViewModel(
} }
private fun downloadNextChapters() { private fun downloadNextChapters() {
if (downloadAheadAmount == 0) return
val manga = manga ?: return val manga = manga ?: return
val amount = downloadPreferences.autoDownloadWhileReading().get()
if (amount == 0 || !manga.favorite) return
// Only download ahead if current + next chapter is already downloaded too to avoid jank // Only download ahead if current + next chapter is already downloaded too to avoid jank
if (getCurrentChapter()?.pageLoader?.isLocal == true) return if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return
viewModelScope.launchIO { viewModelScope.launchIO {
@ -466,7 +437,7 @@ class ReaderViewModel(
} else { } else {
this this
} }
}.take(amount) }.take(downloadAheadAmount)
downloadManager.downloadChapters( downloadManager.downloadChapters(
manga, manga,
@ -507,40 +478,51 @@ class ReaderViewModel(
} }
/** /**
* Called when reader chapter is changed in reader or when activity is paused. * Saves the chapter progress (last read page and whether it's read)
* if incognito mode isn't on.
*/ */
private fun saveReadingProgress() { private suspend fun updateChapterProgress(readerChapter: ReaderChapter, pageIndex: Int) {
mutableState.update {
it.copy(currentPage = pageIndex + 1)
}
if (!incognitoMode) {
readerChapter.requestedPage = pageIndex
readerChapter.chapter.last_page_read = pageIndex
if (readerChapter.pages?.lastIndex == pageIndex) {
readerChapter.chapter.read = true
updateTrackChapterRead(readerChapter)
deleteChapterIfNeeded(readerChapter)
}
updateChapter.await(
ChapterUpdate(
id = readerChapter.chapter.id!!,
read = readerChapter.chapter.read,
bookmark = readerChapter.chapter.bookmark,
lastPageRead = readerChapter.chapter.last_page_read.toLong(),
),
)
}
}
fun restartReadTimer() {
chapterReadStartTime = Date().time
}
fun flushReadTimer() {
getCurrentChapter()?.let { getCurrentChapter()?.let {
viewModelScope.launchNonCancellable { viewModelScope.launchNonCancellable {
saveChapterProgress(it) updateHistory(it)
saveChapterHistory(it)
} }
} }
} }
/** /**
* Saves this [readerChapter] progress (last read page and whether it's read) * Saves the chapter last read history if incognito mode isn't on.
* if incognito mode isn't on.
*/ */
private suspend fun saveChapterProgress(readerChapter: ReaderChapter) { private suspend fun updateHistory(readerChapter: ReaderChapter) {
if (incognitoMode) return
val chapter = readerChapter.chapter
readerChapter.requestedPage = chapter.last_page_read
updateChapter.await(
ChapterUpdate(
id = chapter.id!!,
read = chapter.read,
bookmark = chapter.bookmark,
lastPageRead = chapter.last_page_read.toLong(),
),
)
}
/**
* Saves this [readerChapter] last read history if incognito mode isn't on.
*/
private suspend fun saveChapterHistory(readerChapter: ReaderChapter) {
if (incognitoMode) return if (incognitoMode) return
val chapterId = readerChapter.chapter.id!! val chapterId = readerChapter.chapter.id!!
@ -551,17 +533,6 @@ class ReaderViewModel(
chapterReadStartTime = null chapterReadStartTime = null
} }
fun setReadStartTime() {
chapterReadStartTime = Date().time
}
/**
* Called from the activity to preload the given [chapter].
*/
suspend fun preloadChapter(chapter: ReaderChapter) {
preload(chapter)
}
/** /**
* Called from the activity to load and set the next chapter as active. * Called from the activity to load and set the next chapter as active.
*/ */
@ -714,8 +685,8 @@ class ReaderViewModel(
mutableState.update { it.copy(dialog = Dialog.PageActions(page)) } mutableState.update { it.copy(dialog = Dialog.PageActions(page)) }
} }
fun openColorFilterDialog() { fun openSettingsDialog() {
mutableState.update { it.copy(dialog = Dialog.ColorFilter) } mutableState.update { it.copy(dialog = Dialog.Settings) }
} }
fun closeDialog() { fun closeDialog() {
@ -839,44 +810,14 @@ class ReaderViewModel(
* will run in a background thread and errors are ignored. * will run in a background thread and errors are ignored.
*/ */
private fun updateTrackChapterRead(readerChapter: ReaderChapter) { private fun updateTrackChapterRead(readerChapter: ReaderChapter) {
if (incognitoMode || !hasTrackers) return if (incognitoMode) return
if (!trackPreferences.autoUpdateTrack().get()) return if (!trackPreferences.autoUpdateTrack().get()) return
val manga = manga ?: return val manga = manga ?: return
val chapterRead = readerChapter.chapter.chapter_number.toDouble()
val trackManager = Injekt.get<TrackManager>()
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
viewModelScope.launchNonCancellable { viewModelScope.launchNonCancellable {
getTracks.await(manga.id) trackChapter.await(context, manga.id, readerChapter.chapter.chapter_number.toDouble())
.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged && chapterRead > track.lastChapterRead) {
val updatedTrack = track.copy(lastChapterRead = chapterRead)
// We want these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected.
async {
runCatching {
try {
if (!context.isOnline()) error("Couldn't update tracker as device is offline")
service.update(updatedTrack.toDbTrack(), true)
insertTrack.await(updatedTrack)
} catch (e: Exception) {
delayedTrackingStore.addItem(updatedTrack)
DelayedTrackingUpdateJob.setupTask(context)
throw e
}
}
}
} else {
null
}
}
.awaitAll()
.mapNotNull { it.exceptionOrNull() }
.forEach { logcat(LogPriority.INFO, it) }
} }
} }
@ -922,7 +863,7 @@ class ReaderViewModel(
sealed class Dialog { sealed class Dialog {
object Loading : Dialog() object Loading : Dialog()
object ColorFilter : Dialog() object Settings : Dialog()
data class PageActions(val page: ReaderPage) : Dialog() data class PageActions(val page: ReaderPage) : Dialog()
} }

View File

@ -1,164 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogWindowProvider
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScreen
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import tachiyomi.core.preference.getAndSet
@Composable
fun ReaderColorFilterDialog(
onDismissRequest: () -> Unit,
readerPreferences: ReaderPreferences,
) {
val colorFilterModes = buildList {
addAll(
listOf(
R.string.label_default,
R.string.filter_mode_multiply,
R.string.filter_mode_screen,
),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
addAll(
listOf(
R.string.filter_mode_overlay,
R.string.filter_mode_lighten,
R.string.filter_mode_darken,
),
)
}
}.map { stringResource(it) }
val customBrightness by readerPreferences.customBrightness().collectAsState()
val customBrightnessValue by readerPreferences.customBrightnessValue().collectAsState()
val colorFilter by readerPreferences.colorFilter().collectAsState()
val colorFilterValue by readerPreferences.colorFilterValue().collectAsState()
val colorFilterMode by readerPreferences.colorFilterMode().collectAsState()
AdaptiveSheet(
onDismissRequest = onDismissRequest,
) {
(LocalView.current.parent as? DialogWindowProvider)?.window?.setDimAmount(0f)
CompositionLocalProvider(
LocalPreferenceMinHeight provides 48.dp,
) {
PreferenceScreen(
items = listOfNotNull(
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.customBrightness(),
title = stringResource(R.string.pref_custom_brightness),
),
/**
* Sets the brightness of the screen. Range is [-75, 100].
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
* From 1 to 100 it sets that value as brightness.
* 0 sets system brightness and hides the overlay.
*/
Preference.PreferenceItem.SliderPreference(
value = customBrightnessValue,
title = stringResource(R.string.pref_custom_brightness),
min = -75,
max = 100,
onValueChanged = {
readerPreferences.customBrightnessValue().set(it)
true
},
).takeIf { customBrightness },
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.colorFilter(),
title = stringResource(R.string.pref_custom_color_filter),
),
Preference.PreferenceItem.SliderPreference(
value = colorFilterValue.red,
title = stringResource(R.string.color_filter_r_value),
max = 255,
onValueChanged = { newRValue ->
readerPreferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, RED_MASK, 16)
}
true
},
).takeIf { colorFilter },
Preference.PreferenceItem.SliderPreference(
value = colorFilterValue.green,
title = stringResource(R.string.color_filter_g_value),
max = 255,
onValueChanged = { newRValue ->
readerPreferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, GREEN_MASK, 8)
}
true
},
).takeIf { colorFilter },
Preference.PreferenceItem.SliderPreference(
value = colorFilterValue.blue,
title = stringResource(R.string.color_filter_b_value),
max = 255,
onValueChanged = { newRValue ->
readerPreferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, BLUE_MASK, 0)
}
true
},
).takeIf { colorFilter },
Preference.PreferenceItem.SliderPreference(
value = colorFilterValue.alpha,
title = stringResource(R.string.color_filter_a_value),
max = 255,
onValueChanged = { newRValue ->
readerPreferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, ALPHA_MASK, 24)
}
true
},
).takeIf { colorFilter },
Preference.PreferenceItem.BasicListPreference(
value = colorFilterMode.toString(),
title = stringResource(R.string.pref_color_filter_mode),
entries = colorFilterModes
.mapIndexed { index, mode -> index.toString() to mode }
.toMap(),
onValueChanged = { newValue ->
readerPreferences.colorFilterMode().set(newValue.toInt())
true
},
).takeIf { colorFilter },
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.grayscale(),
title = stringResource(R.string.pref_grayscale),
),
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.invertedColors(),
title = stringResource(R.string.pref_inverted_colors),
),
),
)
}
}
}
private fun getColorValue(currentColor: Int, color: Int, mask: Long, bitShift: Int): Int {
return (color shl bitShift) or (currentColor and mask.inv().toInt())
}
private const val ALPHA_MASK: Long = 0xFF000000
private const val RED_MASK: Long = 0x00FF0000
private const val GREEN_MASK: Long = 0x0000FF00
private const val BLUE_MASK: Long = 0x000000FF

View File

@ -1,53 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ReaderGeneralSettingsBinding
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.preference.bindToPreference
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.injectLazy
/**
* Sheet to show reader and viewer preferences.
*/
class ReaderGeneralSettings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
NestedScrollView(context, attrs) {
private val readerPreferences: ReaderPreferences by injectLazy()
private val binding = ReaderGeneralSettingsBinding.inflate(LayoutInflater.from(context), this, false)
init {
addView(binding.root)
initGeneralPreferences()
}
/**
* Init general reader preferences.
*/
private fun initGeneralPreferences() {
binding.backgroundColor.bindToIntPreference(readerPreferences.readerTheme(), R.array.reader_themes_values)
binding.showPageNumber.bindToPreference(readerPreferences.showPageNumber())
binding.fullscreen.bindToPreference(readerPreferences.fullscreen())
readerPreferences.fullscreen().changes()
.onEach {
// If the preference is explicitly disabled, that means the setting was configured since there is a cutout
binding.cutoutShort.isVisible = it && ((context as ReaderActivity).hasCutout || !readerPreferences.cutoutShort().get())
binding.cutoutShort.bindToPreference(readerPreferences.cutoutShort())
}
.launchIn((context as ReaderActivity).lifecycleScope)
binding.keepscreen.bindToPreference(readerPreferences.keepScreenOn())
binding.longTap.bindToPreference(readerPreferences.readWithLongTap())
binding.alwaysShowChapterTransition.bindToPreference(readerPreferences.alwaysShowChapterTransition())
binding.pageTransitions.bindToPreference(readerPreferences.pageTransitions())
}
}

View File

@ -1,146 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.util.preference.bindToPreference
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.injectLazy
/**
* Sheet to show reader and viewer preferences.
*/
class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
NestedScrollView(context, attrs) {
private val readerPreferences: ReaderPreferences by injectLazy()
private val binding = ReaderReadingModeSettingsBinding.inflate(LayoutInflater.from(context), this, false)
init {
addView(binding.root)
initGeneralPreferences()
when ((context as ReaderActivity).viewModel.state.value.viewer) {
is PagerViewer -> initPagerPreferences()
is WebtoonViewer -> initWebtoonPreferences()
}
}
/**
* Init general reader preferences.
*/
private fun initGeneralPreferences() {
binding.viewer.onItemSelectedListener = { position ->
val readingModeType = ReadingModeType.fromSpinner(position)
(context as ReaderActivity).viewModel.setMangaReadingMode(readingModeType.flagValue)
val mangaViewer = (context as ReaderActivity).viewModel.getMangaReadingMode()
if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) {
initWebtoonPreferences()
} else {
initPagerPreferences()
}
}
binding.viewer.setSelection((context as ReaderActivity).viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
binding.rotationMode.onItemSelectedListener = { position ->
val rotationType = OrientationType.fromSpinner(position)
(context as ReaderActivity).viewModel.setMangaOrientationType(rotationType.flagValue)
}
binding.rotationMode.setSelection((context as ReaderActivity).viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue)
}
/**
* Init the preferences for the pager reader.
*/
private fun initPagerPreferences() {
binding.webtoonPrefsGroup.root.isVisible = false
binding.pagerPrefsGroup.root.isVisible = true
binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
binding.pagerPrefsGroup.navigatePan.bindToPreference(readerPreferences.navigateToPan())
binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager())
readerPreferences.navigationModePager().changes()
.onEach {
val isTappingEnabled = it != 5
binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled
binding.pagerPrefsGroup.navigatePan.isVisible = isTappingEnabled
}
.launchIn((context as ReaderActivity).lifecycleScope)
// Makes so that landscape zoom gets hidden away when image scale type is not fit screen
binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1)
readerPreferences.imageScaleType().changes()
.onEach { binding.pagerPrefsGroup.landscapeZoom.isVisible = it == 1 }
.launchIn((context as ReaderActivity).lifecycleScope)
binding.pagerPrefsGroup.landscapeZoom.bindToPreference(readerPreferences.landscapeZoom())
binding.pagerPrefsGroup.zoomStart.bindToPreference(readerPreferences.zoomStart(), 1)
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
readerPreferences.dualPageSplitPaged().changes()
.onEach {
binding.pagerPrefsGroup.dualPageInvert.isVisible = it
if (it) {
binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
}
}
.launchIn((context as ReaderActivity).lifecycleScope)
binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit())
readerPreferences.dualPageRotateToFit().changes()
.onEach {
binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it
if (it) {
binding.pagerPrefsGroup.dualPageSplit.isChecked = false
}
}
.launchIn((context as ReaderActivity).lifecycleScope)
binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert())
}
/**
* Init the preferences for the webtoon reader.
*/
private fun initWebtoonPreferences() {
binding.pagerPrefsGroup.root.isVisible = false
binding.webtoonPrefsGroup.root.isVisible = true
binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon())
readerPreferences.navigationModeWebtoon().changes()
.onEach { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 }
.launchIn((context as ReaderActivity).lifecycleScope)
binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(readerPreferences.cropBordersWebtoon())
binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(readerPreferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
binding.webtoonPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitWebtoon())
// Makes it so that dual page invert gets hidden away when dual page split is turned off
readerPreferences.dualPageSplitWebtoon().changes()
.onEach { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it }
.launchIn((context as ReaderActivity).lifecycleScope)
binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertWebtoon())
binding.webtoonPrefsGroup.longStripSplit.isVisible = !isReleaseBuildType
binding.webtoonPrefsGroup.longStripSplit.bindToPreference(readerPreferences.longStripSplitWebtoon())
binding.webtoonPrefsGroup.doubleTapZoom.bindToPreference(readerPreferences.webtoonDoubleTapZoomEnabled())
}
}

View File

@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.ui.reader.setting
import cafe.adriel.voyager.core.model.ScreenModel
import eu.kanade.tachiyomi.util.preference.toggle
import tachiyomi.core.preference.Preference
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ReaderSettingsScreenModel(
val preferences: ReaderPreferences = Injekt.get(),
) : ScreenModel {
fun togglePreference(preference: (ReaderPreferences) -> Preference<Boolean>) {
preference(preferences).toggle()
}
}

View File

@ -1,55 +1,136 @@
package eu.kanade.tachiyomi.ui.reader.setting package eu.kanade.tachiyomi.ui.reader.setting
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import androidx.core.view.isVisible
import android.view.View import androidx.lifecycle.lifecycleScope
import android.view.ViewGroup import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.util.preference.bindToPreference
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.injectLazy
class ReaderSettingsSheet( class ReaderSettingsSheet(
private val activity: ReaderActivity, private val activity: ReaderActivity,
) : BaseBottomSheetDialog(activity) { ) : BottomSheetDialog(activity) {
private val tabs = listOf( private val readerPreferences: ReaderPreferences by injectLazy()
ReaderReadingModeSettings(activity) to R.string.pref_category_reading_mode,
ReaderGeneralSettings(activity) to R.string.pref_category_general,
)
private lateinit var binding: CommonTabbedSheetBinding private lateinit var binding: ReaderReadingModeSettingsBinding
override fun createView(inflater: LayoutInflater): View {
binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
val adapter = Adapter()
binding.pager.adapter = adapter
binding.tabs.setupWithViewPager(binding.pager)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
behavior.isFitToContents = false binding = ReaderReadingModeSettingsBinding.inflate(activity.layoutInflater)
behavior.halfExpandedRatio = 0.25f setContentView(binding.root)
initGeneralPreferences()
when (activity.viewModel.state.value.viewer) {
is PagerViewer -> initPagerPreferences()
is WebtoonViewer -> initWebtoonPreferences()
}
} }
private inner class Adapter : ViewPagerAdapter() { private fun initGeneralPreferences() {
binding.viewer.onItemSelectedListener = { position ->
val readingModeType = ReadingModeType.fromSpinner(position)
activity.viewModel.setMangaReadingMode(readingModeType.flagValue)
override fun createView(container: ViewGroup, position: Int): View { val mangaViewer = activity.viewModel.getMangaReadingMode()
return tabs[position].first if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) {
initWebtoonPreferences()
} else {
initPagerPreferences()
}
} }
binding.viewer.setSelection(activity.viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
override fun getCount(): Int { binding.rotationMode.onItemSelectedListener = { position ->
return tabs.size val rotationType = OrientationType.fromSpinner(position)
activity.viewModel.setMangaOrientationType(rotationType.flagValue)
} }
binding.rotationMode.setSelection(activity.viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue)
}
override fun getPageTitle(position: Int): CharSequence { private fun initPagerPreferences() {
return activity.resources!!.getString(tabs[position].second) binding.webtoonPrefsGroup.root.isVisible = false
} binding.pagerPrefsGroup.root.isVisible = true
binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
binding.pagerPrefsGroup.navigatePan.bindToPreference(readerPreferences.navigateToPan())
binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager())
readerPreferences.navigationModePager().changes()
.onEach {
val isTappingEnabled = it != 5
binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled
binding.pagerPrefsGroup.navigatePan.isVisible = isTappingEnabled
}
.launchIn(activity.lifecycleScope)
// Makes so that landscape zoom gets hidden away when image scale type is not fit screen
binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1)
readerPreferences.imageScaleType().changes()
.onEach { binding.pagerPrefsGroup.landscapeZoom.isVisible = it == 1 }
.launchIn(activity.lifecycleScope)
binding.pagerPrefsGroup.landscapeZoom.bindToPreference(readerPreferences.landscapeZoom())
binding.pagerPrefsGroup.zoomStart.bindToPreference(readerPreferences.zoomStart(), 1)
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
readerPreferences.dualPageSplitPaged().changes()
.onEach {
binding.pagerPrefsGroup.dualPageInvert.isVisible = it
if (it) {
binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
}
}
.launchIn(activity.lifecycleScope)
binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit())
readerPreferences.dualPageRotateToFit().changes()
.onEach {
binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it
if (it) {
binding.pagerPrefsGroup.dualPageSplit.isChecked = false
}
}
.launchIn(activity.lifecycleScope)
binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert())
}
private fun initWebtoonPreferences() {
binding.pagerPrefsGroup.root.isVisible = false
binding.webtoonPrefsGroup.root.isVisible = true
binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon())
readerPreferences.navigationModeWebtoon().changes()
.onEach { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 }
.launchIn(activity.lifecycleScope)
binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(readerPreferences.cropBordersWebtoon())
binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(readerPreferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
binding.webtoonPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitWebtoon())
// Makes it so that dual page invert gets hidden away when dual page split is turned off
readerPreferences.dualPageSplitWebtoon().changes()
.onEach { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it }
.launchIn(activity.lifecycleScope)
binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertWebtoon())
binding.webtoonPrefsGroup.longStripSplit.isVisible = !isReleaseBuildType
binding.webtoonPrefsGroup.longStripSplit.bindToPreference(readerPreferences.longStripSplitWebtoon())
binding.webtoonPrefsGroup.doubleTapZoom.bindToPreference(readerPreferences.webtoonDoubleTapZoomEnabled())
} }
} }

View File

@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.AboutScreen
import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen
import eu.kanade.presentation.more.settings.screen.SettingsBackupAndSyncScreen import eu.kanade.presentation.more.settings.screen.SettingsBackupAndSyncScreen
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.presentation.util.LocalBackPress import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen

View File

@ -1,55 +0,0 @@
package eu.kanade.tachiyomi.widget.sheet
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.getElevation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.displayCompat
import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
abstract class BaseBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
abstract fun createView(inflater: LayoutInflater): View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = createView(layoutInflater)
setContentView(rootView)
// Enforce max width for tablets
val width = context.resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
if (width > 0) {
behavior.maxWidth = width
}
// Set peek height to 50% display height
context.displayCompat?.let {
val metrics = DisplayMetrics()
it.getRealMetrics(metrics)
behavior.peekHeight = metrics.heightPixels / 2
}
// Set navbar color to transparent for edge-to-edge bottom sheet if we can use light navigation bar
// TODO Replace deprecated systemUiVisibility when material-components uses new API to modify status bar icons
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.setNavigationBarTransparentCompat(context, behavior.getElevation())
val bottomSheet = rootView.parent as ViewGroup
var flags = bottomSheet.systemUiVisibility
flags = if (context.isNightMode()) {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
} else {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
bottomSheet.systemUiVisibility = flags
}
}
}

View File

@ -1,56 +0,0 @@
package eu.kanade.tachiyomi.widget.sheet
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.viewpager.widget.ViewPager
import java.lang.reflect.Field
/**
* From https://github.com/kafumi/android-bottomsheet-viewpager
*/
class BottomSheetViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : ViewPager(context, attrs) {
private val positionField: Field = LayoutParams::class.java.getDeclaredField("position").also {
it.isAccessible = true
}
override fun getChildAt(index: Int): View {
val currentView = getCurrentView() ?: return super.getChildAt(index)
return if (index == 0) {
currentView
} else {
var view = super.getChildAt(index)
if (view == currentView) {
view = super.getChildAt(0)
}
return view
}
}
private fun getCurrentView(): View? {
for (i in 0 until childCount) {
val child = super.getChildAt(i)
val lp = child.layoutParams as? LayoutParams
if (lp != null) {
val position = positionField.getInt(lp)
if (!lp.isDecor && currentItem == position) {
return child
}
}
}
return null
}
init {
addOnPageChangeListener(
object : SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
requestLayout()
}
},
)
}
}

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z" />
</vector>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/transparent_tabs_background">
<!-- Remove background color so rounded sheet corners work -->
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/menu"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabGravity="fill"
app:tabMode="fixed" />
<ImageButton
android:id="@+id/menu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_menu"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_overflow_24dp"
app:tint="?attr/colorOnSurface"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<eu.kanade.tachiyomi.widget.sheet.BottomSheetViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -119,29 +119,29 @@
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorOnSurface" />
<ImageButton <ImageButton
android:id="@+id/action_settings" android:id="@+id/action_settings_legacy"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_settings" android:contentDescription="@string/action_settings"
android:padding="@dimen/screen_edge_margin" android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toStartOf="@+id/action_color_settings" app:layout_constraintEnd_toStartOf="@+id/action_settings"
app:layout_constraintStart_toEndOf="@id/action_rotation" app:layout_constraintStart_toEndOf="@id/action_rotation"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_settings_24dp" app:srcCompat="@drawable/ic_settings_24dp"
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorOnSurface" />
<ImageButton <ImageButton
android:id="@+id/action_color_settings" android:id="@+id/action_settings"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/custom_filter" android:contentDescription="@string/action_settings"
android:padding="@dimen/screen_edge_margin" android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/action_settings" app:layout_constraintStart_toEndOf="@id/action_settings_legacy"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_brightness_5_24dp" app:srcCompat="@drawable/ic_settings_24dp"
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorOnSurface" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
android:id="@+id/background_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/reader_themes"
app:title="@string/pref_reader_theme" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/show_page_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_show_page_number"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/fullscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_fullscreen"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/cutout_short"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_cutout_short"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/keepscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_keep_screen_on"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/long_tap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_read_with_long_tap"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/always_show_chapter_transition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_always_show_chapter_transition"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/page_transitions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_page_transitions"
android:textColor="?android:attr/textColorSecondary" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -1,5 +1,3 @@
<resources> <resources>
<dimen name="bottom_sheet_width">480dp</dimen>
<dimen name="screen_edge_margin">24dp</dimen> <dimen name="screen_edge_margin">24dp</dimen>
</resources> </resources>

View File

@ -9,20 +9,6 @@
<item>@string/vertical_plus_viewer</item> <item>@string/vertical_plus_viewer</item>
</string-array> </string-array>
<string-array name="reader_themes">
<item>@string/black_background</item>
<item>@string/gray_background</item>
<item>@string/white_background</item>
<item>@string/automatic_background</item>
</string-array>
<string-array name="reader_themes_values">
<item>1</item>
<item>2</item>
<item>0</item>
<item>3</item>
</string-array>
<string-array name="image_scale_type"> <string-array name="image_scale_type">
<item>@string/scale_type_fit_screen</item> <item>@string/scale_type_fit_screen</item>
<item>@string/scale_type_stretch</item> <item>@string/scale_type_stretch</item>

View File

@ -1,6 +1,4 @@
<resources> <resources>
<dimen name="bottom_sheet_width">0dp</dimen>
<dimen name="dialog_radius">8dp</dimen> <dimen name="dialog_radius">8dp</dimen>
<dimen name="screen_edge_margin">16dp</dimen> <dimen name="screen_edge_margin">16dp</dimen>

View File

@ -0,0 +1,16 @@
package tachiyomi.core.preference
enum class TriState {
DISABLED, // Disable filter
ENABLED_IS, // Enabled with "is" filter
ENABLED_NOT, // Enabled with "not" filter
;
fun next(): TriState {
return when (this) {
DISABLED -> ENABLED_IS
ENABLED_IS -> ENABLED_NOT
ENABLED_NOT -> DISABLED
}
}
}

View File

@ -2,7 +2,7 @@ package tachiyomi.data.chapter
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long, Long?) -> Chapter = val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long, Long) -> Chapter =
{ id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload, lastModifiedAt -> { id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload, lastModifiedAt ->
Chapter( Chapter(
id = id, id = id,

View File

@ -4,8 +4,8 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long?) -> Manga = val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?) -> Manga =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt -> { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt ->
Manga( Manga(
id = id, id = id,
source = source, source = source,
@ -28,11 +28,12 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
updateStrategy = updateStrategy, updateStrategy = updateStrategy,
initialized = initialized, initialized = initialized,
lastModifiedAt = lastModifiedAt, lastModifiedAt = lastModifiedAt,
favoriteModifiedAt = favoriteModifiedAt,
) )
} }
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long?, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga = val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category -> { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category ->
LibraryManga( LibraryManga(
manga = mangaMapper( manga = mangaMapper(
id, id,
@ -56,6 +57,7 @@ val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?,
updateStrategy, updateStrategy,
calculateInterval, calculateInterval,
lastModifiedAt, lastModifiedAt,
favoriteModifiedAt,
), ),
category = category, category = category,
totalChapters = totalCount, totalChapters = totalCount,

View File

@ -11,7 +11,7 @@ CREATE TABLE chapters(
source_order INTEGER NOT NULL, source_order INTEGER NOT NULL,
date_fetch INTEGER AS Long NOT NULL, date_fetch INTEGER AS Long NOT NULL,
date_upload INTEGER AS Long NOT NULL, date_upload INTEGER AS Long NOT NULL,
last_modified_at INTEGER AS Long, last_modified_at INTEGER AS Long NOT NULL,
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE ON DELETE CASCADE
); );
@ -60,8 +60,8 @@ DELETE FROM chapters
WHERE _id IN :chapterIds; WHERE _id IN :chapterIds;
insert: insert:
INSERT INTO chapters(manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload) INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at)
VALUES (:mangaId,:url,:name,:scanlator,:read,:bookmark,:lastPageRead,:chapterNumber,:sourceOrder,:dateFetch,:dateUpload); VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, strftime('%s', 'now'));
update: update:
UPDATE chapters UPDATE chapters

View File

@ -23,23 +23,23 @@ CREATE TABLE mangas(
date_added INTEGER AS Long NOT NULL, date_added INTEGER AS Long NOT NULL,
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0,
calculate_interval INTEGER DEFAULT 0 NOT NULL, calculate_interval INTEGER DEFAULT 0 NOT NULL,
last_modified_at INTEGER AS Long last_modified_at INTEGER AS Long NOT NULL,
favorite_modified_at INTEGER AS Long
); );
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
CREATE INDEX mangas_url_index ON mangas(url); CREATE INDEX mangas_url_index ON mangas(url);
CREATE TRIGGER update_last_modified_at_mangas CREATE TRIGGER update_favorite_modified_at_mangas
AFTER UPDATE ON mangas AFTER UPDATE OF favorite ON mangas
FOR EACH ROW
BEGIN BEGIN
UPDATE mangas UPDATE mangas
SET last_modified_at = strftime('%s', 'now') SET favorite_modified_at = strftime('%s', 'now')
WHERE _id = new._id; WHERE _id = new._id;
END; END;
CREATE TRIGGER insert_last_modified_at_mangas CREATE TRIGGER update_last_modified_at_mangas
AFTER INSERT ON mangas AFTER UPDATE ON mangas
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
UPDATE mangas UPDATE mangas
@ -68,6 +68,11 @@ getAllManga:
SELECT * SELECT *
FROM mangas; FROM mangas;
getMangasWithFavoriteTimestamp:
SELECT *
FROM mangas
WHERE favorite_modified_at IS NOT NULL;
getSourceIdWithFavoriteCount: getSourceIdWithFavoriteCount:
SELECT SELECT
source, source,
@ -104,8 +109,8 @@ DELETE FROM mangas
WHERE favorite = 0 AND source IN :sourceIds; WHERE favorite = 0 AND source IN :sourceIds;
insert: insert:
INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added,update_strategy,calculate_interval) INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at)
VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnailUrl,:favorite,:lastUpdate,:nextUpdate,:initialized,:viewerFlags,:chapterFlags,:coverLastModified,:dateAdded,:updateStrategy,:calculateInterval); VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, strftime('%s', 'now'));
update: update:
UPDATE mangas SET UPDATE mangas SET

View File

@ -2,7 +2,7 @@ CREATE TABLE mangas_categories(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL, manga_id INTEGER NOT NULL,
category_id INTEGER NOT NULL, category_id INTEGER NOT NULL,
last_modified_at INTEGER AS Long, last_modified_at INTEGER AS Long NOT NULL,
FOREIGN KEY(category_id) REFERENCES categories (_id) FOREIGN KEY(category_id) REFERENCES categories (_id)
ON DELETE CASCADE, ON DELETE CASCADE,
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
@ -18,18 +18,9 @@ BEGIN
WHERE _id = new._id; WHERE _id = new._id;
END; END;
CREATE TRIGGER insert_last_modified_at_mangas_categories
AFTER INSERT ON mangas_categories
FOR EACH ROW
BEGIN
UPDATE mangas_categories
SET last_modified_at = strftime('%s', 'now')
WHERE _id = new._id;
END;
insert: insert:
INSERT INTO mangas_categories(manga_id, category_id) INSERT INTO mangas_categories(manga_id, category_id, last_modified_at)
VALUES (:mangaId, :categoryId); VALUES (:mangaId, :categoryId, strftime('%s', 'now'));
deleteMangaCategoryByMangaId: deleteMangaCategoryByMangaId:
DELETE FROM mangas_categories DELETE FROM mangas_categories

View File

@ -1,94 +1,12 @@
import kotlin.collections.List; ALTER TABLE mangas ADD COLUMN last_modified_at INTEGER AS Long NOT NULL DEFAULT 0;
import eu.kanade.tachiyomi.source.model.UpdateStrategy; ALTER TABLE mangas ADD COLUMN favorite_modified_at INTEGER AS Long;
ALTER TABLE mangas_categories ADD COLUMN last_modified_at INTEGER AS Long NOT NULL DEFAULT 0;
-- Drop indices ALTER TABLE chapters ADD COLUMN last_modified_at INTEGER AS Long NOT NULL DEFAULT 0;
DROP INDEX IF EXISTS library_favorite_index;
DROP INDEX IF EXISTS mangas_url_index;
DROP INDEX IF EXISTS chapters_manga_id_index;
DROP INDEX IF EXISTS chapters_unread_by_manga_index;
-- Rename existing tables to temporary tables
ALTER TABLE mangas RENAME TO mangas_temp;
ALTER TABLE chapters RENAME TO chapters_temp;
ALTER TABLE mangas_categories RENAME TO mangas_categories_temp;
-- Create new tables with updated schema
CREATE TABLE mangas(
_id INTEGER NOT NULL PRIMARY KEY,
source INTEGER NOT NULL,
url TEXT NOT NULL,
artist TEXT,
author TEXT,
description TEXT,
genre TEXT AS List<String>,
title TEXT NOT NULL,
status INTEGER NOT NULL,
thumbnail_url TEXT,
favorite INTEGER AS Boolean NOT NULL,
last_update INTEGER AS Long,
next_update INTEGER AS Long,
initialized INTEGER AS Boolean NOT NULL,
viewer INTEGER NOT NULL,
chapter_flags INTEGER NOT NULL,
cover_last_modified INTEGER AS Long NOT NULL,
date_added INTEGER AS Long NOT NULL,
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0,
last_modified_at INTEGER AS Long
);
CREATE TABLE mangas_categories(
_id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
last_modified_at INTEGER AS Long,
FOREIGN KEY(category_id) REFERENCES categories (_id)
ON DELETE CASCADE,
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE
);
CREATE TABLE chapters(
_id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL,
url TEXT NOT NULL,
name TEXT NOT NULL,
scanlator TEXT,
read INTEGER AS Boolean NOT NULL,
bookmark INTEGER AS Boolean NOT NULL,
last_page_read INTEGER NOT NULL,
chapter_number REAL AS Float NOT NULL,
source_order INTEGER NOT NULL,
date_fetch INTEGER AS Long NOT NULL,
date_upload INTEGER AS Long NOT NULL,
last_modified_at INTEGER AS Long,
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE
);
-- Copy data from temporary tables to new tables
INSERT INTO mangas
SELECT _id, source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, NULL
FROM mangas_temp;
INSERT INTO chapters
SELECT _id, manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, NULL
FROM chapters_temp;
INSERT INTO mangas_categories
SELECT _id, manga_id, category_id, NULL
FROM mangas_categories_temp;
-- Create indices
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
CREATE INDEX mangas_url_index ON mangas(url);
CREATE INDEX chapters_manga_id_index ON chapters(manga_id);
CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0;
-- Drop temporary tables
DROP TABLE IF EXISTS mangas_temp;
DROP TABLE IF EXISTS chapters_temp;
DROP TABLE IF EXISTS mangas_categories_temp;
UPDATE mangas SET last_modified_at = strftime('%s', 'now');
UPDATE mangas SET favorite_modified_at = strftime('%s', 'now') WHERE favorite = 1;
UPDATE mangas_categories SET last_modified_at = strftime('%s', 'now');
UPDATE chapters SET last_modified_at = strftime('%s', 'now');
-- Create triggers -- Create triggers
DROP TRIGGER IF EXISTS update_last_modified_at_mangas; DROP TRIGGER IF EXISTS update_last_modified_at_mangas;
@ -101,13 +19,12 @@ BEGIN
WHERE _id = new._id; WHERE _id = new._id;
END; END;
DROP TRIGGER IF EXISTS insert_last_modified_at_mangas; DROP TRIGGER IF EXISTS update_favorite_modified_at_mangas;
CREATE TRIGGER insert_last_modified_at_mangas CREATE TRIGGER update_last_favorited_at_mangas
AFTER INSERT ON mangas AFTER UPDATE OF favorite ON mangas
FOR EACH ROW
BEGIN BEGIN
UPDATE mangas UPDATE mangas
SET last_modified_at = strftime('%s', 'now') SET favorite_modified_at = strftime('%s', 'now')
WHERE _id = new._id; WHERE _id = new._id;
END; END;
@ -130,13 +47,3 @@ BEGIN
SET last_modified_at = strftime('%s', 'now') SET last_modified_at = strftime('%s', 'now')
WHERE _id = new._id; WHERE _id = new._id;
END; END;
DROP TRIGGER IF EXISTS insert_last_modified_at_mangas_categories;
CREATE TRIGGER insert_last_modified_at_mangas_categories
AFTER INSERT ON mangas_categories
FOR EACH ROW
BEGIN
UPDATE mangas_categories
SET last_modified_at = strftime('%s', 'now')
WHERE _id = new._id;
END;

View File

@ -13,7 +13,7 @@ data class Chapter(
val dateUpload: Long, val dateUpload: Long,
val chapterNumber: Float, val chapterNumber: Float,
val scanlator: String?, val scanlator: String?,
val lastModifiedAt: Long?, val lastModifiedAt: Long,
) { ) {
val isRecognizedNumber: Boolean val isRecognizedNumber: Boolean
get() = chapterNumber >= 0f get() = chapterNumber >= 0f
@ -32,7 +32,7 @@ data class Chapter(
dateUpload = -1, dateUpload = -1,
chapterNumber = -1f, chapterNumber = -1f,
scanlator = null, scanlator = null,
lastModifiedAt = null, lastModifiedAt = 0,
) )
} }
} }

View File

@ -1,11 +1,11 @@
package tachiyomi.domain.library.service package tachiyomi.domain.library.service
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getEnum import tachiyomi.core.preference.getEnum
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.TriStateFilter
class LibraryPreferences( class LibraryPreferences(
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
@ -49,27 +49,27 @@ class LibraryPreferences(
// region Filter // region Filter
fun filterDownloaded() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriStateFilter.DISABLED) fun filterDownloaded() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriState.DISABLED)
fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriStateFilter.DISABLED) fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriState.DISABLED)
fun filterStarted() = preferenceStore.getEnum("pref_filter_library_started_v2", TriStateFilter.DISABLED) fun filterStarted() = preferenceStore.getEnum("pref_filter_library_started_v2", TriState.DISABLED)
fun filterBookmarked() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriStateFilter.DISABLED) fun filterBookmarked() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriState.DISABLED)
fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED) fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriState.DISABLED)
fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriStateFilter.DISABLED) fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriState.DISABLED)
fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriStateFilter.DISABLED) fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriState.DISABLED)
fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriStateFilter.DISABLED) fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriState.DISABLED)
fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriStateFilter.DISABLED) fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriState.DISABLED)
fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriStateFilter.DISABLED) fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriState.DISABLED)
fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED) fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriState.DISABLED)
// endregion // endregion
@ -134,9 +134,9 @@ class LibraryPreferences(
// region Swipe Actions // region Swipe Actions
fun swipeEndAction() = preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleBookmark) fun swipeToStartAction() = preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleBookmark)
fun swipeStartAction() = preferenceStore.getEnum("pref_chapter_swipe_start_action", ChapterSwipeAction.ToggleRead) fun swipeToEndAction() = preferenceStore.getEnum("pref_chapter_swipe_start_action", ChapterSwipeAction.ToggleRead)
// endregion // endregion

View File

@ -1,6 +1,7 @@
package tachiyomi.domain.manga.model package tachiyomi.domain.manga.model
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.core.preference.TriState
import java.io.Serializable import java.io.Serializable
data class Manga( data class Manga(
@ -24,7 +25,8 @@ data class Manga(
val thumbnailUrl: String?, val thumbnailUrl: String?,
val updateStrategy: UpdateStrategy, val updateStrategy: UpdateStrategy,
val initialized: Boolean, val initialized: Boolean,
val lastModifiedAt: Long?, val lastModifiedAt: Long,
val favoriteModifiedAt: Long?,
) : Serializable { ) : Serializable {
val sorting: Long val sorting: Long
@ -42,18 +44,18 @@ data class Manga(
val bookmarkedFilterRaw: Long val bookmarkedFilterRaw: Long
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
val unreadFilter: TriStateFilter val unreadFilter: TriState
get() = when (unreadFilterRaw) { get() = when (unreadFilterRaw) {
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS CHAPTER_SHOW_UNREAD -> TriState.ENABLED_IS
CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT CHAPTER_SHOW_READ -> TriState.ENABLED_NOT
else -> TriStateFilter.DISABLED else -> TriState.DISABLED
} }
val bookmarkedFilter: TriStateFilter val bookmarkedFilter: TriState
get() = when (bookmarkedFilterRaw) { get() = when (bookmarkedFilterRaw) {
CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS CHAPTER_SHOW_BOOKMARKED -> TriState.ENABLED_IS
CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT CHAPTER_SHOW_NOT_BOOKMARKED -> TriState.ENABLED_NOT
else -> TriStateFilter.DISABLED else -> TriState.DISABLED
} }
fun sortDescending(): Boolean { fun sortDescending(): Boolean {
@ -111,6 +113,7 @@ data class Manga(
updateStrategy = UpdateStrategy.ALWAYS_UPDATE, updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
initialized = false, initialized = false,
lastModifiedAt = 0L, lastModifiedAt = 0L,
favoriteModifiedAt = null,
) )
} }
} }

View File

@ -0,0 +1,9 @@
package tachiyomi.domain.manga.model
import tachiyomi.core.preference.TriState
inline fun applyFilter(filter: TriState, predicate: () -> Boolean): Boolean = when (filter) {
TriState.DISABLED -> true
TriState.ENABLED_IS -> predicate()
TriState.ENABLED_NOT -> !predicate()
}

View File

@ -1,22 +0,0 @@
package tachiyomi.domain.manga.model
enum class TriStateFilter {
DISABLED, // Disable filter
ENABLED_IS, // Enabled with "is" filter
ENABLED_NOT, // Enabled with "not" filter
;
fun next(): TriStateFilter {
return when (this) {
DISABLED -> ENABLED_IS
ENABLED_IS -> ENABLED_NOT
ENABLED_NOT -> DISABLED
}
}
}
inline fun applyFilter(filter: TriStateFilter, predicate: () -> Boolean): Boolean = when (filter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> predicate()
TriStateFilter.ENABLED_NOT -> !predicate()
}

View File

@ -1,7 +1,7 @@
[versions] [versions]
compiler = "1.4.8" compiler = "1.4.8"
compose-bom = "2023.06.00-alpha01" compose-bom = "2023.07.00-alpha01"
accompanist = "0.31.4-beta" accompanist = "0.31.5-beta"
[libraries] [libraries]
activity = "androidx.activity:activity-compose:1.7.2" activity = "androidx.activity:activity-compose:1.7.2"

View File

@ -1,5 +1,5 @@
[versions] [versions]
aboutlib_version = "10.8.0" aboutlib_version = "10.8.2"
okhttp_version = "5.0.0-alpha.11" okhttp_version = "5.0.0-alpha.11"
shizuku_version = "12.2.0" shizuku_version = "12.2.0"
sqlite = "2.3.1" sqlite = "2.3.1"
@ -20,7 +20,7 @@ flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" }
okio = "com.squareup.okio:okio:3.3.0" okio = "com.squareup.okio:okio:3.4.0"
conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2" conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2"
@ -58,9 +58,11 @@ flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013
photoview = "com.github.chrisbanes:PhotoView:2.3.0" photoview = "com.github.chrisbanes:PhotoView:2.3.0"
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
insetter = "dev.chrisbanes.insetter:insetter:0.6.1" insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.3" compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.4"
compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0" compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0"
swipe = "me.saket.swipe:swipe:1.2.0"
logcat = "com.squareup.logcat:logcat:0.1" logcat = "com.squareup.logcat:logcat:0.1"
acra-http = "ch.acra:acra-http:5.10.1" acra-http = "ch.acra:acra-http:5.10.1"

View File

@ -831,4 +831,5 @@
<string name="skipped_reason_not_in_release_period">Sha omès perquè no se nespera cap publicació avui</string> <string name="skipped_reason_not_in_release_period">Sha omès perquè no se nespera cap publicació avui</string>
<string name="action_filter_interval_passed">Període de comprovació superat</string> <string name="action_filter_interval_passed">Període de comprovació superat</string>
<string name="pref_update_release_grace_period_info">Es recomana un període de gràcia baix per a minimitzar la sobrecàrrega de les fonts. Com més comprovacions fallides es produeixin, més interval hi haurà entre comprovacions, fins a un màxim de 28 dies.</string> <string name="pref_update_release_grace_period_info">Es recomana un període de gràcia baix per a minimitzar la sobrecàrrega de les fonts. Com més comprovacions fallides es produeixin, més interval hi haurà entre comprovacions, fins a un màxim de 28 dies.</string>
<string name="delete_downloaded">Suprimeix els baixats</string>
</resources> </resources>

View File

@ -639,7 +639,7 @@
<string name="pref_verbose_logging_summary">Vypisovat podrobné informace do systémového protokolu (sníží výkon aplikace)</string> <string name="pref_verbose_logging_summary">Vypisovat podrobné informace do systémového protokolu (sníží výkon aplikace)</string>
<string name="channel_app_updates">Aktualizace aplikace</string> <string name="channel_app_updates">Aktualizace aplikace</string>
<string name="notification_size_warning">Varování: velké aktualizace poškozují zdroje a můžou vést k pomalejším aktualizacím a zvýšenému využití baterie. Klepnutím se dozvíte více.</string> <string name="notification_size_warning">Varování: velké aktualizace poškozují zdroje a můžou vést k pomalejším aktualizacím a zvýšenému využití baterie. Klepnutím se dozvíte více.</string>
<string name="pref_auto_clear_chapter_cache">Vymazat mezipaměť kapitol při zavření aplikace</string> <string name="pref_auto_clear_chapter_cache">Vymazat mezipaměť kapitol při spuštění aplikace</string>
<string name="extension_api_error">Chyba v získání seznamu rozšíření</string> <string name="extension_api_error">Chyba v získání seznamu rozšíření</string>
<string name="privacy_policy">Zásady ochrany osobních údajů</string> <string name="privacy_policy">Zásady ochrany osobních údajů</string>
<string name="clear_database_source_item_count">%1$d neknihovní záznamy v databázi</string> <string name="clear_database_source_item_count">%1$d neknihovní záznamy v databázi</string>
@ -849,4 +849,5 @@
<string name="track_delete_remote_text">Odebrat také z %s</string> <string name="track_delete_remote_text">Odebrat také z %s</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="track_delete_title">Odebrat sledování %s\?</string> <string name="track_delete_title">Odebrat sledování %s\?</string>
<string name="delete_downloaded">Odstranit stažené</string>
</resources> </resources>

View File

@ -565,7 +565,7 @@
<string name="pref_appearance_summary">Темӑ, кун тата вӑхӑт хармачӗ</string> <string name="pref_appearance_summary">Темӑ, кун тата вӑхӑт хармачӗ</string>
<string name="pref_library_summary">Пухмӑшсем, пӗтӗмӗшле ҫӗнетӳ</string> <string name="pref_library_summary">Пухмӑшсем, пӗтӗмӗшле ҫӗнетӳ</string>
<string name="pref_reader_summary">Вулав тытӑмӗ, кӑтартӑнни, куҫӑм</string> <string name="pref_reader_summary">Вулав тытӑмӗ, кӑтартӑнни, куҫӑм</string>
<string name="label_default">Пӳрӳлле</string> <string name="label_default">Йаланхилле</string>
<string name="action_open_random_manga">Ӑнсӑрт ҫырав уҫ</string> <string name="action_open_random_manga">Ӑнсӑрт ҫырав уҫ</string>
<string name="theme_strawberrydaiquiri">Ҫӗр ҫырли тайккирийӗ</string> <string name="theme_strawberrydaiquiri">Ҫӗр ҫырли тайккирийӗ</string>
<string name="theme_midnightdusk">Ҫур ҫӗр ӗнтрӗкӗ</string> <string name="theme_midnightdusk">Ҫур ҫӗр ӗнтрӗкӗ</string>
@ -630,4 +630,5 @@
<string name="ext_info_version">Версси</string> <string name="ext_info_version">Версси</string>
<string name="pref_library_update_refresh_trackers">Йӗрлеве хӑй-хальлӗн ҫӗнетни</string> <string name="pref_library_update_refresh_trackers">Йӗрлеве хӑй-хальлӗн ҫӗнетни</string>
<string name="ext_update_all">Пурне те ҫӗнет</string> <string name="ext_update_all">Пурне те ҫӗнет</string>
<string name="delete_downloaded">Тийесе илнисене катерт</string>
</resources> </resources>

View File

@ -386,7 +386,7 @@
<string name="add_to_library">Zur Bibliothek hinzufügen</string> <string name="add_to_library">Zur Bibliothek hinzufügen</string>
<string name="confirm_exit">Zum Beenden nochmal die Zurück-Taste drücken</string> <string name="confirm_exit">Zum Beenden nochmal die Zurück-Taste drücken</string>
<string name="information_webview_required">WebView ist für Tachiyomi erforderlich</string> <string name="information_webview_required">WebView ist für Tachiyomi erforderlich</string>
<string name="licenses">Quelloffene Lizenzen</string> <string name="licenses">Open-Source-Lizenzen</string>
<string name="website">Webseite</string> <string name="website">Webseite</string>
<string name="label_downloaded_only">Nur Heruntergeladenes</string> <string name="label_downloaded_only">Nur Heruntergeladenes</string>
<string name="recent_manga_time">Kap. %1$s - %2$s</string> <string name="recent_manga_time">Kap. %1$s - %2$s</string>
@ -628,7 +628,7 @@
<string name="download_queue_size_warning">Achtung: Große Downloads könnten dazu führen, dass Quellen langsamer werden und/oder Tachiyomi blockieren. Tippe, um mehr zu erfahren.</string> <string name="download_queue_size_warning">Achtung: Große Downloads könnten dazu führen, dass Quellen langsamer werden und/oder Tachiyomi blockieren. Tippe, um mehr zu erfahren.</string>
<string name="ext_update_all">Alle aktualisieren</string> <string name="ext_update_all">Alle aktualisieren</string>
<string name="channel_app_updates">Anwendungsaktualisierungen</string> <string name="channel_app_updates">Anwendungsaktualisierungen</string>
<string name="pref_auto_clear_chapter_cache">Kapitel-Zwischenspeicher beim Schließen der App löschen</string> <string name="pref_auto_clear_chapter_cache">Kapitel-Zwischenspeicher beim Öffnen der App löschen</string>
<string name="clear_database_source_item_count">%1$d Nicht-Bibliothekseinträge in der Datenbank</string> <string name="clear_database_source_item_count">%1$d Nicht-Bibliothekseinträge in der Datenbank</string>
<string name="database_clean">Nichts zu bereinigen</string> <string name="database_clean">Nichts zu bereinigen</string>
<string name="extension_api_error">Herunterladen der Erweiterungsliste ist fehlgeschlagen</string> <string name="extension_api_error">Herunterladen der Erweiterungsliste ist fehlgeschlagen</string>
@ -831,4 +831,5 @@
<string name="track_delete_remote_text">Auch aus %s entfernen</string> <string name="track_delete_remote_text">Auch aus %s entfernen</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="track_delete_title">Tracking von %s entfernen\?</string> <string name="track_delete_title">Tracking von %s entfernen\?</string>
<string name="delete_downloaded">Heruntergeladenes löschen</string>
</resources> </resources>

View File

@ -628,7 +628,7 @@
<string name="download_queue_size_warning">Προειδοποίηση: οι μαζικές λήψεις ενδέχεται να οδηγήσουν σε επιβράδυνση των πηγών ή/και αποκλεισμό του Tachiyomi. Πατήστε για να μάθετε περισσότερα.</string> <string name="download_queue_size_warning">Προειδοποίηση: οι μαζικές λήψεις ενδέχεται να οδηγήσουν σε επιβράδυνση των πηγών ή/και αποκλεισμό του Tachiyomi. Πατήστε για να μάθετε περισσότερα.</string>
<string name="ext_update_all">Ενημέρωση όλων</string> <string name="ext_update_all">Ενημέρωση όλων</string>
<string name="channel_app_updates">Ενημερώσεις εφαρμογής</string> <string name="channel_app_updates">Ενημερώσεις εφαρμογής</string>
<string name="pref_auto_clear_chapter_cache">Εκκαθάριση της προσωρινής μνήμης κεφαλαίων στο κλείσιμο της εφαρμογής</string> <string name="pref_auto_clear_chapter_cache">Εκκαθάριση της προσωρινής μνήμης κεφαλαίων κατά την εκκίνηση της εφαρμογής</string>
<string name="clear_database_source_item_count">%1$d καταχωρήσεις εκτός βιβλιοθήκης στη βάση δεδομένων</string> <string name="clear_database_source_item_count">%1$d καταχωρήσεις εκτός βιβλιοθήκης στη βάση δεδομένων</string>
<string name="database_clean">Τίποτα προς εκκαθάριση</string> <string name="database_clean">Τίποτα προς εκκαθάριση</string>
<string name="extension_api_error">Απέτυχε η λήψη λίστας επεκτάσεων</string> <string name="extension_api_error">Απέτυχε η λήψη λίστας επεκτάσεων</string>
@ -831,4 +831,5 @@
<string name="track_delete_title">Κατάργηση παρακολούθησης %s;</string> <string name="track_delete_title">Κατάργηση παρακολούθησης %s;</string>
<string name="track_delete_remote_text">Επίσης, αφαιρέστε από %s</string> <string name="track_delete_remote_text">Επίσης, αφαιρέστε από %s</string>
<string name="track_delete_text">Αυτό θα καταργήσει την παρακολούθηση τοπικά.</string> <string name="track_delete_text">Αυτό θα καταργήσει την παρακολούθηση τοπικά.</string>
<string name="delete_downloaded">Διαγραφή ληφθέντων</string>
</resources> </resources>

View File

@ -671,7 +671,7 @@
<string name="download_queue_size_warning">Advertencia: Las descargas grandes pueden llevar a que las fuentes se vuelvan cada vez más lentas y en casos extremos que los servidores limiten o impidan el acceso a Tachiyomi. Toca aquí para más información.</string> <string name="download_queue_size_warning">Advertencia: Las descargas grandes pueden llevar a que las fuentes se vuelvan cada vez más lentas y en casos extremos que los servidores limiten o impidan el acceso a Tachiyomi. Toca aquí para más información.</string>
<string name="ext_update_all">Actualizar todas</string> <string name="ext_update_all">Actualizar todas</string>
<string name="channel_app_updates">Actualizaciones de la aplicación</string> <string name="channel_app_updates">Actualizaciones de la aplicación</string>
<string name="pref_auto_clear_chapter_cache">Borrar la caché de capítulos al cerrar la aplicación</string> <string name="pref_auto_clear_chapter_cache">Borrar la caché de capítulos al abrir la aplicación</string>
<string name="database_clean">Base de datos limpia</string> <string name="database_clean">Base de datos limpia</string>
<string name="clear_database_source_item_count">Hay %1$d elementos en la base de datos que no están en la biblioteca</string> <string name="clear_database_source_item_count">Hay %1$d elementos en la base de datos que no están en la biblioteca</string>
<string name="extension_api_error">No se pudo descargar el listado de extensiones</string> <string name="extension_api_error">No se pudo descargar el listado de extensiones</string>
@ -880,4 +880,5 @@
<string name="track_delete_title">¿Quitar el rastreo de %s\?</string> <string name="track_delete_title">¿Quitar el rastreo de %s\?</string>
<string name="track_delete_text">Esto eliminará el seguimiento localmente.</string> <string name="track_delete_text">Esto eliminará el seguimiento localmente.</string>
<string name="track_delete_remote_text">Quitar también de %s</string> <string name="track_delete_remote_text">Quitar también de %s</string>
<string name="delete_downloaded">Borrar los ya descargados</string>
</resources> </resources>

View File

@ -64,7 +64,7 @@
<string name="action_sort_total">Dami ng kabanata</string> <string name="action_sort_total">Dami ng kabanata</string>
<string name="action_sort_alpha">Pa-alpabeto</string> <string name="action_sort_alpha">Pa-alpabeto</string>
<string name="action_filter_empty">Tanggalin ang pansala</string> <string name="action_filter_empty">Tanggalin ang pansala</string>
<string name="action_filter_unread">Babasahin</string> <string name="action_filter_unread">Hindi Nabasa</string>
<string name="action_filter_bookmarked">Tinandaan</string> <string name="action_filter_bookmarked">Tinandaan</string>
<string name="action_filter">Pansala</string> <string name="action_filter">Pansala</string>
<string name="action_menu">Menu</string> <string name="action_menu">Menu</string>
@ -801,10 +801,10 @@
<string name="pref_chapter_swipe_end">Mag-swipe ng pakanang pagkilos</string> <string name="pref_chapter_swipe_end">Mag-swipe ng pakanang pagkilos</string>
<string name="action_set_interval">Itakda ang pagitan</string> <string name="action_set_interval">Itakda ang pagitan</string>
<string name="action_filter_interval_custom">Sinadyang takdang pagkuha</string> <string name="action_filter_interval_custom">Sinadyang takdang pagkuha</string>
<string name="action_filter_interval_late">Nahuling 10+ pagsusuri</string> <string name="action_filter_interval_late">Huling 10+ pagsusuri</string>
<string name="action_sort_next_updated">Susunod na inaasahang update</string> <string name="action_sort_next_updated">Susunod na inaasahang update</string>
<string name="action_filter_interval_dropped">Nawala\? or Nahulog\? (depending on the context, \"Nahulog\" means dropped or dropped something, and \"Nawala\" means Gone/Vanished) Nahuling 20+ at 2 buwan</string> <string name="action_filter_interval_dropped">Nawala\? or Nahulog\? (depending on the context, \"Nahulog\" means dropped or dropped something, and \"Nawala\" means Gone/Vanished) Nahuling 20+ at 2 buwan</string>
<string name="action_filter_interval_passed">Lumipas ang check period</string> <string name="action_filter_interval_passed">Lumipas na panahon ng pagsuri</string>
<string name="action_filter_interval_long">Kunin kada buwan (kada ika-28 na araw)</string> <string name="action_filter_interval_long">Kunin kada buwan (kada ika-28 na araw)</string>
<plurals name="pref_update_release_following_days"> <plurals name="pref_update_release_following_days">
<item quantity="one">pagkatapos ng %d araw</item> <item quantity="one">pagkatapos ng %d araw</item>

View File

@ -314,7 +314,7 @@
<string name="theme_system">सिस्टम का पालन करें</string> <string name="theme_system">सिस्टम का पालन करें</string>
<string name="pref_manage_notifications">सूचनाओं का प्रबंधन</string> <string name="pref_manage_notifications">सूचनाओं का प्रबंधन</string>
<string name="pref_category_security">सुरक्षा और गोपनीयता</string> <string name="pref_category_security">सुरक्षा और गोपनीयता</string>
<string name="lock_with_biometrics">अनलॉक की आवश्यकता है</string> <string name="lock_with_biometrics">खोलने की आवश्यकता है</string>
<string name="lock_when_idle">निष्क्रिय होने पर लॉक करें</string> <string name="lock_when_idle">निष्क्रिय होने पर लॉक करें</string>
<string name="lock_always">हमेशा</string> <string name="lock_always">हमेशा</string>
<string name="lock_never">कभी नहीँ</string> <string name="lock_never">कभी नहीँ</string>
@ -739,4 +739,9 @@
<string name="pref_page_rotate">फिट होने के लिए चौड़े पृष्ठों को घुमाएं</string> <string name="pref_page_rotate">फिट होने के लिए चौड़े पृष्ठों को घुमाएं</string>
<string name="pref_backup_summary">मैनुअल और स्वचालित बैकअप</string> <string name="pref_backup_summary">मैनुअल और स्वचालित बैकअप</string>
<string name="pref_security_summary">ऐप लॉक, सुरक्षित स्क्रीन</string> <string name="pref_security_summary">ऐप लॉक, सुरक्षित स्क्रीन</string>
<string name="action_set_interval">अंतराल निर्धारित करें</string>
<string name="action_filter_interval_custom">अनुकूलित लाने का अंतराल</string>
<string name="action_filter_interval_long">मासिक प्राप्त करें (28 दिन)</string>
<string name="action_filter_interval_late">देर से 10+ की जाँच</string>
<string name="action_filter_interval_dropped">छोड़ा हुआ\? देर से 20+ और 2 महीने</string>
</resources> </resources>

View File

@ -673,7 +673,7 @@
<string name="download_queue_size_warning">Attenzione: grossi download di massa possono rallentare le fonti e/o bloccare tachiyomi. Tocca per saperne di più.</string> <string name="download_queue_size_warning">Attenzione: grossi download di massa possono rallentare le fonti e/o bloccare tachiyomi. Tocca per saperne di più.</string>
<string name="ext_update_all">Aggiorna tutto</string> <string name="ext_update_all">Aggiorna tutto</string>
<string name="channel_app_updates">Aggiornamenti dell\'applicazione</string> <string name="channel_app_updates">Aggiornamenti dell\'applicazione</string>
<string name="pref_auto_clear_chapter_cache">Cancella la cache capitoli alla chiusura dell\'app</string> <string name="pref_auto_clear_chapter_cache">Cancella la cache capitoli all\'avvio dell\'app</string>
<string name="clear_database_source_item_count">%1$d voci non presenti in libreria nel database</string> <string name="clear_database_source_item_count">%1$d voci non presenti in libreria nel database</string>
<string name="database_clean">Niente da Pulire</string> <string name="database_clean">Niente da Pulire</string>
<string name="extension_api_error">Impossibile ottenere l\'elenco estensioni</string> <string name="extension_api_error">Impossibile ottenere l\'elenco estensioni</string>
@ -882,4 +882,5 @@
<string name="track_delete_remote_text">Rimuovi anche da %s</string> <string name="track_delete_remote_text">Rimuovi anche da %s</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="track_delete_text">Questo rimuoverà il tracciamento locale.</string> <string name="track_delete_text">Questo rimuoverà il tracciamento locale.</string>
<string name="delete_downloaded">Cancella scaricati</string>
</resources> </resources>

View File

@ -616,7 +616,7 @@
<string name="download_queue_size_warning">Amaran: muat turun secara pukal besar boleh menyebabkan sumber menjadi lebih perlahan dan/atau menyekat Tachiyomi. Ketik untuk ketahui selebihnya.</string> <string name="download_queue_size_warning">Amaran: muat turun secara pukal besar boleh menyebabkan sumber menjadi lebih perlahan dan/atau menyekat Tachiyomi. Ketik untuk ketahui selebihnya.</string>
<string name="ext_update_all">Kemas kini semua</string> <string name="ext_update_all">Kemas kini semua</string>
<string name="channel_app_updates">Kemas kini aplikasi</string> <string name="channel_app_updates">Kemas kini aplikasi</string>
<string name="pref_auto_clear_chapter_cache">Hapus cache bab apabila menutup aplikasi</string> <string name="pref_auto_clear_chapter_cache">Hapus cache bab apabila membuka aplikasi</string>
<string name="database_clean">Tiada apa untuk dibersihkan</string> <string name="database_clean">Tiada apa untuk dibersihkan</string>
<string name="clear_database_source_item_count">%1$d entri bukan pustaka dalam pangkalan data</string> <string name="clear_database_source_item_count">%1$d entri bukan pustaka dalam pangkalan data</string>
<string name="extension_api_error">Gagal mendapatkan senarai sambungan</string> <string name="extension_api_error">Gagal mendapatkan senarai sambungan</string>
@ -813,4 +813,5 @@
<string name="track_delete_text">Ini akan membuang penjejakan secara lokal.</string> <string name="track_delete_text">Ini akan membuang penjejakan secara lokal.</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="track_delete_remote_text">Juga buang daripada %s</string> <string name="track_delete_remote_text">Juga buang daripada %s</string>
<string name="delete_downloaded">Padam dimuat turun</string>
</resources> </resources>

View File

@ -421,7 +421,7 @@
<string name="notification_chapters_single">अध्याय %1$s</string> <string name="notification_chapters_single">अध्याय %1$s</string>
<string name="scale_type_original_size">मूल आकार</string> <string name="scale_type_original_size">मूल आकार</string>
<string name="pref_zoom_start">जूम सुरु स्थिति</string> <string name="pref_zoom_start">जूम सुरु स्थिति</string>
<string name="pref_auto_clear_chapter_cache">एप बन्दमा अध्याय क्यास खाली गर्नुहोस्</string> <string name="pref_auto_clear_chapter_cache">एप खोलेमा अध्याय क्यास खाली गर्नुहोस्</string>
<string name="information_webview_outdated">राम्रो संगतताको लागि कृपया WebView एप अपडेट गर्नुहोस्</string> <string name="information_webview_outdated">राम्रो संगतताको लागि कृपया WebView एप अपडेट गर्नुहोस्</string>
<string name="pref_library_update_manga_restriction">इन्ट्री अपडेट गर्न छोड्नुहोस्</string> <string name="pref_library_update_manga_restriction">इन्ट्री अपडेट गर्न छोड्नुहोस्</string>
<string name="pref_update_only_completely_read">नपढेको अध्याय(हरू) सँग</string> <string name="pref_update_only_completely_read">नपढेको अध्याय(हरू) सँग</string>
@ -696,7 +696,7 @@
<string name="confirm_add_duplicate_manga">तपाईको पुस्तकालयमा एउटै नामको इन्ट्री छ। <string name="confirm_add_duplicate_manga">तपाईको पुस्तकालयमा एउटै नामको इन्ट्री छ।
\n \n
\nके तपाईं अझै जारी राख्न चाहनुहुन्छ\?</string> \nके तपाईं अझै जारी राख्न चाहनुहुन्छ\?</string>
<string name="reading_list">पढ्ने सूची</string> <string name="reading_list">पढिरहेको सूची</string>
<string name="updates_last_update_info">पुस्तकालय पछिल्लो पटक अपडेट गरिएको: %s</string> <string name="updates_last_update_info">पुस्तकालय पछिल्लो पटक अपडेट गरिएको: %s</string>
<string name="crash_screen_description">%s एक अप्रत्याशित त्रुटिमा पर्यो। समर्थन को लागि हामी तपाईंलाई हाम्रो Discord को #support च्यानलमा क्र्यास लगहरू साझेदारी गर्न सुझाव दिन्छौं।</string> <string name="crash_screen_description">%s एक अप्रत्याशित त्रुटिमा पर्यो। समर्थन को लागि हामी तपाईंलाई हाम्रो Discord को #support च्यानलमा क्र्यास लगहरू साझेदारी गर्न सुझाव दिन्छौं।</string>
<string name="update_check_open">GitHub मा खोल्नुहोस्</string> <string name="update_check_open">GitHub मा खोल्नुहोस्</string>
@ -831,4 +831,5 @@
<string name="track_delete_remote_text">%s बाट पनि हटाउनुहोस्</string> <string name="track_delete_remote_text">%s बाट पनि हटाउनुहोस्</string>
<string name="track_delete_text">यसले लोकल रूपमा ट्र्याकिङ हटाउनेछ।</string> <string name="track_delete_text">यसले लोकल रूपमा ट्र्याकिङ हटाउनेछ।</string>
<string name="action_ok">ठीक छ</string> <string name="action_ok">ठीक छ</string>
<string name="delete_downloaded">डाउनलोड गरिएको मेट्नुहोस्</string>
</resources> </resources>

View File

@ -640,7 +640,7 @@
<string name="download_queue_size_warning">Aviso: grandes downloads em massa podem levar as fontes a ficarem lentas e/ou começarem a bloquear o Tachiyomi. Toque para saber mais.</string> <string name="download_queue_size_warning">Aviso: grandes downloads em massa podem levar as fontes a ficarem lentas e/ou começarem a bloquear o Tachiyomi. Toque para saber mais.</string>
<string name="ext_update_all">Atualizar tudo</string> <string name="ext_update_all">Atualizar tudo</string>
<string name="channel_app_updates">Atualizações do aplicativo</string> <string name="channel_app_updates">Atualizações do aplicativo</string>
<string name="pref_auto_clear_chapter_cache">Limpar o cache de capítulos ao fechar o aplicativo</string> <string name="pref_auto_clear_chapter_cache">Limpar o cache de capítulos ao abrir o aplicativo</string>
<string name="clear_database_source_item_count">%1$d itens que não estão na biblioteca no banco de dados</string> <string name="clear_database_source_item_count">%1$d itens que não estão na biblioteca no banco de dados</string>
<string name="database_clean">Nada a ser limpo</string> <string name="database_clean">Nada a ser limpo</string>
<string name="extension_api_error">Erro ao obter a lista de extensões</string> <string name="extension_api_error">Erro ao obter a lista de extensões</string>
@ -841,4 +841,5 @@
<string name="track_delete_title">Remover o monitoramento do %s\?</string> <string name="track_delete_title">Remover o monitoramento do %s\?</string>
<string name="track_delete_text">Isso irá remover o monitoramento localmente.</string> <string name="track_delete_text">Isso irá remover o monitoramento localmente.</string>
<string name="track_delete_remote_text">Também remover do %s</string> <string name="track_delete_remote_text">Também remover do %s</string>
<string name="delete_downloaded">Deletar os disponíveis offline</string>
</resources> </resources>

View File

@ -652,7 +652,7 @@
<string name="download_queue_size_warning">Предупреждение: Большое количество загрузок может привести к замедлению работы источников и/или блокировке Tachiyomi. Нажмите для подробностей.</string> <string name="download_queue_size_warning">Предупреждение: Большое количество загрузок может привести к замедлению работы источников и/или блокировке Tachiyomi. Нажмите для подробностей.</string>
<string name="ext_update_all">Обновить все</string> <string name="ext_update_all">Обновить все</string>
<string name="channel_app_updates">Обновления приложения</string> <string name="channel_app_updates">Обновления приложения</string>
<string name="pref_auto_clear_chapter_cache">Очищать кэш глав при закрытии приложения</string> <string name="pref_auto_clear_chapter_cache">Очищать кэш глав при запуске приложения</string>
<string name="clear_database_source_item_count">%1$d не библиотечных серий в базе данных</string> <string name="clear_database_source_item_count">%1$d не библиотечных серий в базе данных</string>
<string name="database_clean">Нечего очищать</string> <string name="database_clean">Нечего очищать</string>
<string name="extension_api_error">Не удалось получить список расширений</string> <string name="extension_api_error">Не удалось получить список расширений</string>
@ -867,4 +867,5 @@
<string name="track_delete_text">Это удалит отслеживание локально.</string> <string name="track_delete_text">Это удалит отслеживание локально.</string>
<string name="track_delete_remote_text">Также удалить из %s</string> <string name="track_delete_remote_text">Также удалить из %s</string>
<string name="action_ok">ОК</string> <string name="action_ok">ОК</string>
<string name="delete_downloaded">Удалить загруженное</string>
</resources> </resources>

View File

@ -567,7 +567,7 @@
<string name="cover_saved">Cobertedda sarvada</string> <string name="cover_saved">Cobertedda sarvada</string>
<string name="manga_cover">Cobertedda</string> <string name="manga_cover">Cobertedda</string>
<string name="tracking_guide">Ghia pro s\'arrastamentu</string> <string name="tracking_guide">Ghia pro s\'arrastamentu</string>
<string name="categorized_display_settings">Impostatziones de ordinamentu e visualizatzione pro categoria</string> <string name="categorized_display_settings">Impostatziones de ordinamentu pro categoria</string>
<string name="information_empty_category_dialog">Non tenes galu peruna categoria.</string> <string name="information_empty_category_dialog">Non tenes galu peruna categoria.</string>
<string name="action_start_downloading_now">Incumintza a iscarrigare como</string> <string name="action_start_downloading_now">Incumintza a iscarrigare como</string>
<string name="theme_tako">Tako</string> <string name="theme_tako">Tako</string>
@ -643,7 +643,7 @@
<string name="cancelled">Annullada</string> <string name="cancelled">Annullada</string>
<string name="webtoon_side_padding_5">5%</string> <string name="webtoon_side_padding_5">5%</string>
<string name="action_show_manga">Ammustra s\'elementu</string> <string name="action_show_manga">Ammustra s\'elementu</string>
<string name="pref_landscape_zoom">Ismànnia s\'immàgine in orizontale</string> <string name="pref_landscape_zoom">Ismànnia in automàticu sas immàgines largas</string>
<string name="action_display_cover_only_grid">Grìllia cun coberteddas ebbia</string> <string name="action_display_cover_only_grid">Grìllia cun coberteddas ebbia</string>
<string name="pref_update_only_started">No incumintzadas</string> <string name="pref_update_only_started">No incumintzadas</string>
<string name="pref_navigate_pan">Iscurre sas pàginas largas</string> <string name="pref_navigate_pan">Iscurre sas pàginas largas</string>
@ -799,4 +799,36 @@
<string name="pref_chapter_swipe">Iscurrimentu de capìtulu</string> <string name="pref_chapter_swipe">Iscurrimentu de capìtulu</string>
<string name="pref_chapter_swipe_start">Atzione de iscurrimentu a manca</string> <string name="pref_chapter_swipe_start">Atzione de iscurrimentu a manca</string>
<string name="pref_debug_info">Informatziones de depuratzione de còdighe</string> <string name="pref_debug_info">Informatziones de depuratzione de còdighe</string>
<string name="manga_display_interval_title">Càrcula cada</string>
<string name="action_filter_interval_custom">Intervallu de recùperu personalizadu</string>
<string name="action_filter_interval_long">Recùpera cada mese (28 dies)</string>
<string name="action_filter_interval_dropped">Abbandonadu\? In ritardu de 20+ dies e 2 meses</string>
<string name="action_sort_next_updated">Agiornamentu imbente prevìdidu</string>
<string name="pref_update_only_in_release_period">Foras de su perìodu de publicatzione prevìdidu</string>
<string name="intervals_header">Intervallos</string>
<string name="manga_display_modified_interval_title">Imposta s\'agiornamentu pro cada</string>
<string name="manga_modify_interval_title">Modìfica s\'intervallu</string>
<string name="skipped_reason_not_in_release_period">Brincadu ca non bi fiat peruna publicatzione prevìdida oe</string>
<string name="action_set_interval">Imposta s\'intervallu</string>
<string name="action_filter_interval_late">Verìfica in ritardu de 10+ dies</string>
<string name="action_filter_interval_passed">Perìodu de controllu coladu</string>
<string name="manga_modify_calculated_interval_title">Personaliza s\'intervallu</string>
<plurals name="pref_update_release_leading_days">
<item quantity="one">%d die in antis</item>
<item quantity="other">%d dies in antis</item>
</plurals>
<plurals name="pref_update_release_following_days">
<item quantity="one">%d die a pustis</item>
<item quantity="other">%d dies a pustis</item>
</plurals>
<string name="pref_update_release_grace_period_info">Unu perìodu de gràtzia bassu est cussigiadu pro minimare sa pressione subra sas fontes. Prus controllos pro un\'elementu si perdent, prus longu at a èssere s\'intervallu intre sos controllos cun unu màssimu de 28 dies.</string>
<string name="action_ok">AB</string>
<string name="pref_update_release_grace_period">Perìodu de gràtzia prevìdidu pro sa publicatzione</string>
<string name="track_delete_title">Bogare s\'arrastadore de %s\?</string>
<string name="track_delete_text">Custu at a bogare s\'arrastamentu locale.</string>
<string name="track_delete_remote_text">Boga fintzas dae %s</string>
<plurals name="day">
<item quantity="one">1 die</item>
<item quantity="other">%d dies</item>
</plurals>
</resources> </resources>

View File

@ -830,4 +830,5 @@
<string name="skipped_reason_not_in_release_period">Atlandı çünkü bugün bir yayın beklenmiyordu</string> <string name="skipped_reason_not_in_release_period">Atlandı çünkü bugün bir yayın beklenmiyordu</string>
<string name="track_delete_text">Bu, izlemeyi yerel olarak kaldıracak.</string> <string name="track_delete_text">Bu, izlemeyi yerel olarak kaldıracak.</string>
<string name="track_delete_remote_text">Ayrıca şuradan da kaldır: %s</string> <string name="track_delete_remote_text">Ayrıca şuradan da kaldır: %s</string>
<string name="manga_display_modified_interval_title">Güncelleme aralığını ayarla</string>
</resources> </resources>

View File

@ -616,7 +616,7 @@
<string name="download_queue_size_warning">警告:批量下载可能导致图源变慢,甚至会使得它们屏蔽 Tachiyomi。点击了解详情。</string> <string name="download_queue_size_warning">警告:批量下载可能导致图源变慢,甚至会使得它们屏蔽 Tachiyomi。点击了解详情。</string>
<string name="ext_update_all">全部更新</string> <string name="ext_update_all">全部更新</string>
<string name="channel_app_updates">应用更新</string> <string name="channel_app_updates">应用更新</string>
<string name="pref_auto_clear_chapter_cache">关闭应用时清除章节缓存</string> <string name="pref_auto_clear_chapter_cache">启动时清除章节缓存</string>
<string name="database_clean">无需清理</string> <string name="database_clean">无需清理</string>
<string name="clear_database_source_item_count">数据库中有 %1$d 部作品未添加到书架</string> <string name="clear_database_source_item_count">数据库中有 %1$d 部作品未添加到书架</string>
<string name="extension_api_error">无法获取插件列表</string> <string name="extension_api_error">无法获取插件列表</string>
@ -797,4 +797,5 @@
<string name="track_delete_title">要删除 %s 的记录吗?</string> <string name="track_delete_title">要删除 %s 的记录吗?</string>
<string name="track_delete_remote_text">同时删除 %s 上的数据</string> <string name="track_delete_remote_text">同时删除 %s 上的数据</string>
<string name="track_delete_text">将会在本地删除进度记录的关联。</string> <string name="track_delete_text">将会在本地删除进度记录的关联。</string>
<string name="delete_downloaded">删除已下载章节</string>
</resources> </resources>

View File

@ -616,7 +616,7 @@
<string name="download_queue_size_warning">警告:大量批次下載可能壅塞來源並 (或) 使其封鎖 Tachiyomi。輕觸以瞭解詳情。</string> <string name="download_queue_size_warning">警告:大量批次下載可能壅塞來源並 (或) 使其封鎖 Tachiyomi。輕觸以瞭解詳情。</string>
<string name="ext_update_all">全部更新</string> <string name="ext_update_all">全部更新</string>
<string name="channel_app_updates">應用程式更新</string> <string name="channel_app_updates">應用程式更新</string>
<string name="pref_auto_clear_chapter_cache">結束應用程式時清除章節快取</string> <string name="pref_auto_clear_chapter_cache">啟動應用程式時清除章節快取</string>
<string name="clear_database_source_item_count">資料庫中有 %1$d 部作品不屬於藏書</string> <string name="clear_database_source_item_count">資料庫中有 %1$d 部作品不屬於藏書</string>
<string name="database_clean">無須清理</string> <string name="database_clean">無須清理</string>
<string name="extension_api_error">擴充套件清單取得失敗</string> <string name="extension_api_error">擴充套件清單取得失敗</string>
@ -631,7 +631,7 @@
<string name="action_faq_and_guides">常見問題與指南</string> <string name="action_faq_and_guides">常見問題與指南</string>
<string name="webtoon_side_padding_5">5%</string> <string name="webtoon_side_padding_5">5%</string>
<string name="action_show_manga">顯示作品</string> <string name="action_show_manga">顯示作品</string>
<string name="pref_landscape_zoom">縮放橫向圖片</string> <string name="pref_landscape_zoom">自動縮放寬頁</string>
<string name="pref_navigate_pan">導覽寬頁時先平移後翻頁</string> <string name="pref_navigate_pan">導覽寬頁時先平移後翻頁</string>
<string name="action_display_cover_only_grid">純封面格狀</string> <string name="action_display_cover_only_grid">純封面格狀</string>
<string name="pref_update_only_started">無已讀的章節</string> <string name="pref_update_only_started">無已讀的章節</string>
@ -785,4 +785,8 @@
<string name="pref_chapter_swipe">目錄滑動動作</string> <string name="pref_chapter_swipe">目錄滑動動作</string>
<string name="pref_library_columns_per_row">每列 %d 欄</string> <string name="pref_library_columns_per_row">每列 %d 欄</string>
<string name="action_ok">確定</string> <string name="action_ok">確定</string>
<string name="delete_downloaded">刪除舊有下載章節</string>
<string name="track_delete_title">移除「%s」歷程平台</string>
<string name="track_delete_text">這將在本機上解除登錄該歷程平台。</string>
<string name="track_delete_remote_text">同時移除「%s」上的資料</string>
</resources> </resources>

View File

@ -12,6 +12,7 @@
<string name="manga">Library entries</string> <string name="manga">Library entries</string>
<string name="chapters">Chapters</string> <string name="chapters">Chapters</string>
<string name="track">Tracking</string> <string name="track">Tracking</string>
<string name="delete_downloaded">Delete downloaded</string>
<string name="history">History</string> <string name="history">History</string>
<!-- Screen titles --> <!-- Screen titles -->
@ -380,7 +381,7 @@
<string name="tapping_inverted_vertical">Vertical</string> <string name="tapping_inverted_vertical">Vertical</string>
<string name="tapping_inverted_both">Both</string> <string name="tapping_inverted_both">Both</string>
<string name="pref_reader_actions">Actions</string> <string name="pref_reader_actions">Actions</string>
<string name="pref_read_with_long_tap">Show on long tap</string> <string name="pref_read_with_long_tap">Show actions on long tap</string>
<string name="pref_create_folder_per_manga">Save pages into separate folders</string> <string name="pref_create_folder_per_manga">Save pages into separate folders</string>
<string name="pref_create_folder_per_manga_summary">Creates folders according to entries\' title</string> <string name="pref_create_folder_per_manga_summary">Creates folders according to entries\' title</string>
<string name="pref_reader_theme">Background color</string> <string name="pref_reader_theme">Background color</string>
@ -474,7 +475,7 @@
<item quantity="one">Next unread chapter</item> <item quantity="one">Next unread chapter</item>
<item quantity="other">Next %d unread chapters</item> <item quantity="other">Next %d unread chapters</item>
</plurals> </plurals>
<string name="download_ahead_info">Only works on entries in library and if the current chapter plus the next one are already downloaded</string> <string name="download_ahead_info">Only works if the current chapter + the next one are already downloaded.</string>
<string name="save_chapter_as_cbz">Save as CBZ archive</string> <string name="save_chapter_as_cbz">Save as CBZ archive</string>
<string name="split_tall_images">Split tall images</string> <string name="split_tall_images">Split tall images</string>
<string name="split_tall_images_summary">Improves reader performance</string> <string name="split_tall_images_summary">Improves reader performance</string>
@ -570,7 +571,7 @@
<string name="used_cache">Used: %1$s</string> <string name="used_cache">Used: %1$s</string>
<string name="cache_deleted">Cache cleared. %1$d files have been deleted</string> <string name="cache_deleted">Cache cleared. %1$d files have been deleted</string>
<string name="cache_delete_error">Error occurred while clearing</string> <string name="cache_delete_error">Error occurred while clearing</string>
<string name="pref_auto_clear_chapter_cache">Clear chapter cache on app close</string> <string name="pref_auto_clear_chapter_cache">Clear chapter cache on app launch</string>
<string name="pref_invalidate_download_cache">Invalidate downloads index</string> <string name="pref_invalidate_download_cache">Invalidate downloads index</string>
<string name="pref_invalidate_download_cache_summary">Force app to recheck downloaded chapters</string> <string name="pref_invalidate_download_cache_summary">Force app to recheck downloaded chapters</string>
<string name="pref_clear_database">Clear database</string> <string name="pref_clear_database">Clear database</string>

View File

@ -21,6 +21,8 @@ android {
} }
dependencies { dependencies {
implementation(project(":core"))
// Compose // Compose
implementation(platform(compose.bom)) implementation(platform(compose.bom))
implementation(compose.activity) implementation(compose.activity)

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