Merge branch 'master' into sync-part-1

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

View File

@@ -22,8 +22,8 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
versionCode = 103
versionCode = 104
versionName = "0.14.6"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@@ -239,6 +239,7 @@ dependencies {
implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion)
implementation(libs.compose.simpleicons)
implementation(libs.swipe)
// Logging
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.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.track.interactor.TrackChapter
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl
@@ -109,6 +110,7 @@ class DomainModule : InjektModule {
addFactory { GetApplicationRelease(get(), get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(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 tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
import tachiyomi.core.preference.TriState
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.TriStateFilter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -20,19 +20,19 @@ val Manga.readingModeType: Long
val Manga.orientationType: Long
get() = viewerFlags and OrientationType.MASK.toLong()
val Manga.downloadedFilter: TriStateFilter
val Manga.downloadedFilter: TriState
get() {
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
if (forceDownloaded()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
else -> TriStateFilter.DISABLED
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
else -> TriState.DISABLED
}
}
fun Manga.chaptersFiltered(): Boolean {
return unreadFilter != TriStateFilter.DISABLED ||
downloadedFilter != TriStateFilter.DISABLED ||
bookmarkedFilter != TriStateFilter.DISABLED
return unreadFilter != TriState.DISABLED ||
downloadedFilter != TriState.DISABLED ||
bookmarkedFilter != TriState.DISABLED
}
fun Manga.forceDownloaded(): Boolean {
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.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.util.system.LocaleHelper
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.padding

View File

@@ -1,6 +1,7 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
@@ -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.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
@Composable

View File

@@ -1,6 +1,7 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -12,7 +13,6 @@ import eu.kanade.presentation.library.components.MangaListItem
import kotlinx.coroutines.flow.StateFlow
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.util.plus
@Composable

View File

@@ -3,6 +3,7 @@ package eu.kanade.presentation.category
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
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.ui.category.CategoryScreenState
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.padding
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.icons.Icons
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.MoreVert
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.vector.ImageVector
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R
@@ -62,7 +65,7 @@ fun AppBar(
subtitle: String? = null,
// Up button
navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Outlined.ArrowBack,
navigationIcon: ImageVector? = null,
// Menu
actions: @Composable RowScope.() -> Unit = {},
// Action mode
@@ -107,7 +110,7 @@ fun AppBar(
titleContent: @Composable () -> Unit,
// Up button
navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Outlined.ArrowBack,
navigationIcon: ImageVector? = null,
// Menu
actions: @Composable RowScope.() -> Unit = {},
// Action mode
@@ -131,10 +134,7 @@ fun AppBar(
} else {
navigateUp?.let {
IconButton(onClick = it) {
Icon(
imageVector = navigationIcon,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
UpIcon(navigationIcon)
}
}
}
@@ -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 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.Row
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
@@ -42,13 +43,13 @@ fun TabbedDialog(
onDismissRequest: () -> Unit,
tabTitles: List<String>,
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
pagerState: PagerState = rememberPagerState { tabTitles.size },
content: @Composable (Int) -> Unit,
) {
AdaptiveSheet(
onDismissRequest = onDismissRequest,
) {
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState { tabTitles.size }
Column {
Row {

View File

@@ -14,21 +14,21 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.presentation.components.TriStateItem
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
import tachiyomi.core.preference.TriState
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.RadioItem
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
@Composable
fun LibrarySettingsDialog(
@@ -74,7 +74,7 @@ private fun ColumnScope.FilterPage(
TriStateItem(
label = stringResource(R.string.label_downloaded),
state = if (downloadedOnly) {
TriStateFilter.ENABLED_IS
TriState.ENABLED_IS
} else {
filterDownloaded
},

View File

@@ -27,20 +27,20 @@ import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.forceDownloaded
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.presentation.components.TriStateItem
import eu.kanade.tachiyomi.R
import tachiyomi.core.preference.TriState
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.presentation.core.components.RadioItem
import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
@Composable
fun ChapterSettingsDialog(
onDismissRequest: () -> Unit,
manga: Manga? = null,
onDownloadFilterChanged: (TriStateFilter) -> Unit,
onUnreadFilterChanged: (TriStateFilter) -> Unit,
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
onDownloadFilterChanged: (TriState) -> Unit,
onUnreadFilterChanged: (TriState) -> Unit,
onBookmarkedFilterChanged: (TriState) -> Unit,
onSortModeChanged: (Long) -> Unit,
onDisplayModeChanged: (Long) -> Unit,
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
@@ -78,11 +78,11 @@ fun ChapterSettingsDialog(
when (page) {
0 -> {
FilterPage(
downloadFilter = manga?.downloadedFilter ?: TriStateFilter.DISABLED,
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
unreadFilter = manga?.unreadFilter ?: TriStateFilter.DISABLED,
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
onUnreadFilterChanged = onUnreadFilterChanged,
bookmarkedFilter = manga?.bookmarkedFilter ?: TriStateFilter.DISABLED,
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
)
}
@@ -106,12 +106,12 @@ fun ChapterSettingsDialog(
@Composable
private fun ColumnScope.FilterPage(
downloadFilter: TriStateFilter,
onDownloadFilterChanged: ((TriStateFilter) -> Unit)?,
unreadFilter: TriStateFilter,
onUnreadFilterChanged: (TriStateFilter) -> Unit,
bookmarkedFilter: TriStateFilter,
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
downloadFilter: TriState,
onDownloadFilterChanged: ((TriState) -> Unit)?,
unreadFilter: TriState,
onUnreadFilterChanged: (TriState) -> Unit,
bookmarkedFilter: TriState,
onBookmarkedFilterChanged: (TriState) -> Unit,
) {
TriStateItem(
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.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
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.manga.model.Manga
import tachiyomi.domain.source.model.StubSource
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
@@ -87,8 +87,8 @@ fun MangaScreen(
dateRelativeTime: Int,
dateFormat: DateFormat,
isTabletUi: Boolean,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@@ -141,8 +141,8 @@ fun MangaScreen(
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
@@ -175,8 +175,8 @@ fun MangaScreen(
state = state,
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
@@ -214,8 +214,8 @@ private fun MangaScreenSmallImpl(
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@@ -258,7 +258,7 @@ private fun MangaScreenSmallImpl(
) {
val chapterListState = rememberLazyListState()
val chapters = remember(state) { state.processedChapters.toList() }
val chapters = remember(state) { state.processedChapters }
val internalOnBackPressed = {
if (chapters.fastAny { it.selected }) {
@@ -320,7 +320,7 @@ private fun MangaScreenSmallImpl(
) {
ExtendedFloatingActionButton(
text = {
val id = if (chapters.fastAny { it.chapter.read }) {
val id = if (state.chapters.fastAny { it.chapter.read }) {
R.string.action_resume
} else {
R.string.action_start
@@ -421,8 +421,8 @@ private fun MangaScreenSmallImpl(
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected,
@@ -440,8 +440,8 @@ fun MangaScreenLargeImpl(
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@@ -485,7 +485,7 @@ fun MangaScreenLargeImpl(
val layoutDirection = LocalLayoutDirection.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()
var topBarHeight by remember { mutableIntStateOf(0) }
@@ -555,7 +555,7 @@ fun MangaScreenLargeImpl(
) {
ExtendedFloatingActionButton(
text = {
val id = if (chapters.fastAny { it.chapter.read }) {
val id = if (state.chapters.fastAny { it.chapter.read }) {
R.string.action_resume
} else {
R.string.action_start
@@ -641,8 +641,8 @@ fun MangaScreenLargeImpl(
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected,
@@ -703,8 +703,8 @@ private fun LazyListScope.sharedChapterItems(
chapters: List<ChapterItem>,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
@@ -751,8 +751,8 @@ private fun LazyListScope.sharedChapterItems(
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress },
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = {
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)

View File

@@ -1,20 +1,12 @@
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.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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.filled.Bookmark
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.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -37,14 +28,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
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.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource
@@ -53,10 +48,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R
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.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable
fun MangaChapterListItem(
@@ -71,13 +69,19 @@ fun MangaChapterListItem(
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onLongClick: () -> Unit,
onClick: () -> Unit,
onDownloadClick: ((ChapterDownloadAction) -> 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
val configuration = LocalViewConfiguration.current
CompositionLocalProvider(
@@ -85,247 +89,188 @@ fun MangaChapterListItem(
override val touchSlop: Float = configuration.touchSlop * 3f
},
) {
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
val chapterSwipeStartEnabled = chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled
val chapterSwipeEndEnabled = chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled
val dismissState = rememberDismissState()
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 start = getSwipeAction(
action = chapterSwipeStartAction,
read = read,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
)
val dismissContentAlpha = if (lastDismissDirection != null) animateDismissContentAlpha else 1f
val backgroundColor = if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) {
MaterialTheme.colorScheme.primary
} else if (chapterSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) {
MaterialTheme.colorScheme.primary
} else {
Color.Unspecified
val end = getSwipeAction(
action = chapterSwipeEndAction,
read = read,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
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) {
when (dismissState.currentValue) {
DismissValue.DismissedToEnd -> {
lastDismissDirection = DismissDirection.StartToEnd
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onChapterSwipe(chapterSwipeEndAction)
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.DismissedToStart -> {
lastDismissDirection = DismissDirection.EndToStart
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onChapterSwipe(chapterSwipeStartAction)
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.Default -> { }
}
}
SwipeToDismiss(
state = dismissState,
directions = dismissDirections,
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor),
SwipeableActionsBox(
modifier = Modifier.clipToBounds(),
state = swipeableActionsState,
startActions = listOfNotNull(start),
endActions = listOfNotNull(end),
swipeThreshold = swipeActionThreshold,
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
) {
Row(
modifier = modifier
.selectedBackground(selected)
.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),
) {
if (dismissState.dismissDirection in dismissDirections) {
val downloadState = downloadStateProvider()
SwipeBackgroundIcon(
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(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
var textHeight by remember { mutableIntStateOf(0) }
if (!read) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unread),
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 },
var textHeight by remember { mutableIntStateOf(0) }
if (!read) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unread),
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 },
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (readProgress != null || scanlator != null) DotSeparatorText()
}
if (readProgress != null) {
Text(
text = readProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
if (scanlator != null) DotSeparatorText()
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (readProgress != null || scanlator != null) DotSeparatorText()
}
if (readProgress != null) {
Text(
text = readProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
if (scanlator != null) DotSeparatorText()
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
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 SwipeBackgroundIcon(
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.ChapterSwipeAction,
private fun getSwipeAction(
action: LibraryPreferences.ChapterSwipeAction,
read: Boolean,
bookmark: Boolean,
downloadState: Download.State,
) {
val imageVector = when (swipeAction) {
LibraryPreferences.ChapterSwipeAction.ToggleRead -> {
if (!read) {
Icons.Outlined.Done
} else {
Icons.Outlined.RemoveDone
}
}
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> {
if (!bookmark) {
Icons.Outlined.BookmarkAdd
} else {
Icons.Outlined.BookmarkRemove
}
}
LibraryPreferences.ChapterSwipeAction.Download -> {
when (downloadState) {
Download.State.NOT_DOWNLOADED,
Download.State.ERROR,
-> { Icons.Outlined.Download }
Download.State.QUEUE,
Download.State.DOWNLOADING,
-> { Icons.Outlined.FileDownloadOff }
Download.State.DOWNLOADED -> { Icons.Outlined.Delete }
}
}
background: Color,
onSwipe: () -> Unit,
): me.saket.swipe.SwipeAction? {
return when (action) {
LibraryPreferences.ChapterSwipeAction.ToggleRead -> swipeAction(
icon = if (!read) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
background = background,
isUndo = read,
onSwipe = onSwipe,
)
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> swipeAction(
icon = if (!bookmark) Icons.Outlined.BookmarkAdd else Icons.Outlined.BookmarkRemove,
background = background,
isUndo = bookmark,
onSwipe = onSwipe,
)
LibraryPreferences.ChapterSwipeAction.Download -> swipeAction(
icon = when (downloadState) {
Download.State.NOT_DOWNLOADED, Download.State.ERROR -> Icons.Outlined.Download
Download.State.QUEUE, Download.State.DOWNLOADING -> Icons.Outlined.FileDownloadOff
Download.State.DOWNLOADED -> Icons.Outlined.Delete
},
background = background,
onSwipe = onSwipe,
)
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.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
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.AppBarActions
import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.theme.active
@@ -67,10 +66,7 @@ fun MangaToolbar(
},
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
imageVector = if (isActionMode) Icons.Outlined.Close else Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
UpIcon(Icons.Outlined.Close.takeIf { isActionMode })
}
},
actions = {

View File

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

View File

@@ -28,6 +28,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R

View File

@@ -281,8 +281,8 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(R.string.pref_chapter_swipe),
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEndAction(),
title = stringResource(R.string.pref_chapter_swipe_end),
pref = libraryPreferences.swipeToStartAction(),
title = stringResource(R.string.pref_chapter_swipe_start),
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@@ -291,8 +291,8 @@ object SettingsLibraryScreen : SearchableSettings {
),
),
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeStartAction(),
title = stringResource(R.string.pref_chapter_swipe_start),
pref = libraryPreferences.swipeToEndAction(),
title = stringResource(R.string.pref_chapter_swipe_end),
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
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.isSystemInDarkTheme
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
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.Code
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.SettingsBackupRestore
import androidx.compose.material.icons.outlined.Sync
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -45,11 +44,12 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
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.util.LocalBackPress
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
@@ -94,10 +94,7 @@ object SettingsMainScreen : Screen() {
},
navigationIcon = {
IconButton(onClick = backPress::invoke) {
Icon(
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
UpIcon()
}
},
actions = {

View File

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

View File

@@ -12,7 +12,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.Preference
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.tachiyomi.R
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.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
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.map
import kotlinx.coroutines.flow.stateIn
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
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.unit.dp
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.util.isScrolledToEnd
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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
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.util.toDurationString
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.material.padding
import java.util.Locale
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.tachiyomi.crash.CrashActivity
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.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer
@@ -54,7 +53,6 @@ import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.conscrypt.Conscrypt
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.widget.TachiyomiWidgetManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -64,11 +62,9 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val basePreferences: BasePreferences by injectLazy()
private val libraryPreferences: LibraryPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
private val chapterCache: ChapterCache by injectLazy()
@SuppressLint("LaunchActivityFromNotification")
override fun onCreate() {
@@ -172,10 +168,6 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
override fun onStop(owner: LifecycleOwner) {
SecureActivityDelegate.onApplicationStopped()
if (libraryPreferences.autoClearChapterCache().get()) {
chapterCache.clear()
}
}
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.workManager
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getEnum
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences
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
object Migrations {
@@ -350,12 +349,12 @@ object Migrations {
remove(key)
val newValue = when (pref.get()) {
1 -> TriStateFilter.ENABLED_IS
2 -> TriStateFilter.ENABLED_NOT
else -> TriStateFilter.DISABLED
1 -> TriState.ENABLED_IS
2 -> TriState.ENABLED_NOT
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
@ProtoNumber(9) var chapterNumber: Float = 0F,
@ProtoNumber(10) var sourceOrder: Long = 0,
@ProtoNumber(11) var lastModifiedAt: Long? = null,
@ProtoNumber(11) var lastModifiedAt: Long = 0,
) {
fun toChapterImpl(): Chapter {
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(
url = url,
name = name,

View File

@@ -39,7 +39,8 @@ data class BackupManga(
@ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
@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 {
return Manga.create().copy(
@@ -58,6 +59,7 @@ data class BackupManga(
chapterFlags = this@BackupManga.chapterFlags.toLong(),
updateStrategy = this@BackupManga.updateStrategy,
lastModifiedAt = this@BackupManga.lastModifiedAt,
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
)
}
@@ -92,6 +94,7 @@ data class BackupManga(
chapterFlags = manga.chapterFlags.toInt(),
updateStrategy = manga.updateStrategy,
lastModifiedAt = manga.lastModifiedAt,
favoriteModifiedAt = manga.favoriteModifiedAt,
)
}
}

View File

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

View File

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

View File

@@ -7,9 +7,6 @@ import android.view.View
import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.foundation.layout.fillMaxSize
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.Text
import androidx.compose.material3.TopAppBar
@@ -21,7 +18,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity
@@ -38,6 +34,7 @@ import androidx.preference.forEach
import androidx.preference.getOnBindEditTextListener
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
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()) },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
UpIcon()
}
},
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.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
@@ -12,15 +13,18 @@ import uy.kohesive.injekt.injectLazy
object MigrationFlags {
private const val CHAPTERS = 0b0001
private const val CATEGORIES = 0b0010
private const val TRACK = 0b0100
private const val CUSTOM_COVER = 0b1000
private const val CHAPTERS = 0b00001
private const val CATEGORIES = 0b00010
private const val TRACK = 0b00100
private const val CUSTOM_COVER = 0b01000
private const val DELETE_DOWNLOADED = 0b10000
private val coverCache: CoverCache by injectLazy()
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 {
return value and CHAPTERS != 0
@@ -38,23 +42,36 @@ object MigrationFlags {
return value and CUSTOM_COVER != 0
}
fun hasDeleteDownloaded(value: Int): Boolean {
return value and DELETE_DOWNLOADED != 0
}
fun getEnabledFlagsPositions(value: Int): List<Int> {
return flags.mapIndexedNotNull { index, flag -> if (value and flag != 0) index else null }
}
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> {
enableFlags.add(CHAPTERS)
enableFlags.add(CATEGORIES)
val titles = arrayOf(R.string.chapters, R.string.categories).toMutableList()
if (manga != null) {
if (runBlocking { getTracks.await(manga.id) }.isNotEmpty()) {
titles.add(R.string.track)
enableFlags.add(TRACK)
}
if (manga.hasCustomCover(coverCache)) {
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()

View File

@@ -34,6 +34,7 @@ import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.R
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.TrackManager
import eu.kanade.tachiyomi.source.Source
@@ -161,6 +162,7 @@ internal fun MigrateDialog(
internal class MigrateDialogScreenModel(
private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
@@ -219,6 +221,7 @@ internal class MigrateDialogScreenModel(
val migrateCategories = MigrationFlags.hasCategories(flags)
val migrateTracks = MigrationFlags.hasTracks(flags)
val migrateCustomCover = MigrationFlags.hasCustomCover(flags)
val deleteDownloaded = MigrationFlags.hasDeleteDownloaded(flags)
try {
syncChaptersWithSource.await(sourceChapters, newManga, newSource)
@@ -283,6 +286,13 @@ internal class MigrateDialogScreenModel(
insertTrack.awaitAll(tracks)
}
// Delete downloaded
if (deleteDownloaded) {
if (oldSource != null) {
downloadManager.deleteManga(oldManga, oldSource)
}
}
if (replace) {
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.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
@@ -15,18 +16,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.source.model.Filter
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.CollapsibleBox
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.TextItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.Button
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) {
Filter.TriState.STATE_IGNORE -> TriStateFilter.DISABLED
Filter.TriState.STATE_INCLUDE -> TriStateFilter.ENABLED_IS
Filter.TriState.STATE_EXCLUDE -> TriStateFilter.ENABLED_NOT
Filter.TriState.STATE_IGNORE -> TriState.DISABLED
Filter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS
Filter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT
else -> throw IllegalStateException("Unknown TriState state: $this")
}
}
private fun TriStateFilter.toTriStateInt(): Int {
private fun TriState.toTriStateInt(): Int {
return when (this) {
TriStateFilter.DISABLED -> Filter.TriState.STATE_IGNORE
TriStateFilter.ENABLED_IS -> Filter.TriState.STATE_INCLUDE
TriStateFilter.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE
TriState.DISABLED -> Filter.TriState.STATE_IGNORE
TriState.ENABLED_IS -> Filter.TriState.STATE_INCLUDE
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.update
import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.TriState
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
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.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.TriStateFilter
import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracksPerManga
@@ -153,7 +153,7 @@ class LibraryScreenModel(
prefs.filterBookmarked,
prefs.filterCompleted,
) + trackFilter.values
).any { it != TriStateFilter.DISABLED }
).any { it != TriState.DISABLED }
}
.distinctUntilChanged()
.onEach {
@@ -169,12 +169,12 @@ class LibraryScreenModel(
*/
private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Long>>,
loggedInTrackServices: Map<Long, TriStateFilter>,
loggedInTrackServices: Map<Long, TriState>,
): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first()
val downloadedOnly = prefs.globalFilterDownloaded
val filterDownloaded =
if (downloadedOnly) TriStateFilter.ENABLED_IS else prefs.filterDownloaded
if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded
val filterUnread = prefs.filterUnread
val filterStarted = prefs.filterStarted
val filterBookmarked = prefs.filterBookmarked
@@ -182,8 +182,8 @@ class LibraryScreenModel(
val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_NOT) it.key else null }
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_IS) 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 == TriState.ENABLED_IS) it.key else null }
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
val filterFnDownloaded: (LibraryItem) -> Boolean = {
@@ -308,11 +308,11 @@ class LibraryScreenModel(
localBadge = it[1] as Boolean,
languageBadge = it[2] as Boolean,
globalFilterDownloaded = it[3] as Boolean,
filterDownloaded = it[4] as TriStateFilter,
filterUnread = it[5] as TriStateFilter,
filterStarted = it[6] as TriStateFilter,
filterBookmarked = it[7] as TriStateFilter,
filterCompleted = it[8] as TriStateFilter,
filterDownloaded = it[4] as TriState,
filterUnread = it[5] as TriState,
filterStarted = it[6] as TriState,
filterBookmarked = it[7] as TriState,
filterCompleted = it[8] as TriState,
)
},
)
@@ -365,7 +365,7 @@ class LibraryScreenModel(
*
* @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 }
return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices
@@ -670,11 +670,11 @@ class LibraryScreenModel(
val languageBadge: Boolean,
val globalFilterDownloaded: Boolean,
val filterDownloaded: TriStateFilter,
val filterUnread: TriStateFilter,
val filterStarted: TriStateFilter,
val filterBookmarked: TriStateFilter,
val filterCompleted: TriStateFilter,
val filterDownloaded: TriState,
val filterUnread: TriState,
val filterStarted: TriState,
val filterBookmarked: TriState,
val filterCompleted: TriState,
)
@Immutable

View File

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

View File

@@ -63,6 +63,7 @@ import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
@@ -105,6 +106,7 @@ class MainActivity : BaseActivity() {
private val preferences: BasePreferences 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.
var ready = false
@@ -112,12 +114,14 @@ class MainActivity : BaseActivity() {
private var navigator: Navigator? = null
override fun onCreate(savedInstanceState: Bundle?) {
val isLaunch = savedInstanceState == null
// 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)
val didMigration = if (savedInstanceState == null) {
val didMigration = if (isLaunch) {
Migrations.upgrade(
context = applicationContext,
basePreferences = preferences,
@@ -149,7 +153,7 @@ class MainActivity : BaseActivity() {
val downloadOnly by preferences.downloadedOnly().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 isSystemInDarkTheme = isSystemInDarkTheme()
val statusBarBackgroundColor = when {
@@ -189,7 +193,7 @@ class MainActivity : BaseActivity() {
LaunchedEffect(navigator) {
this@MainActivity.navigator = navigator
if (savedInstanceState == null) {
if (isLaunch) {
// Set start screen
handleIntentAction(intent, navigator)
@@ -267,6 +271,10 @@ class MainActivity : BaseActivity() {
elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
}
setSplashScreenExitAnimation(splashScreen)
if (isLaunch && libraryPreferences.autoClearChapterCache().get()) {
chapterCache.clear()
}
}
override fun onProvideAssistContent(outContent: AssistContent) {
@@ -279,7 +287,7 @@ class MainActivity : BaseActivity() {
}
@Composable
fun HandleOnNewIntent(context: Context, navigator: Navigator) {
private fun HandleOnNewIntent(context: Context, navigator: Navigator) {
LaunchedEffect(Unit) {
callbackFlow<Intent> {
val componentActivity = context as ComponentActivity

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.stringResource
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.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
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.ViewerChapters
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.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
@@ -235,13 +237,18 @@ class ReaderActivity : BaseActivity() {
readingModeToast?.cancel()
}
override fun onPause() {
viewModel.flushReadTimer()
super.onPause()
}
/**
* Set menu visibility again on activity resume to apply immersive mode again if needed.
* Helps with rotations.
*/
override fun onResume() {
super.onResume()
viewModel.setReadStartTime()
viewModel.restartReadTimer()
setMenuVisibility(viewModel.state.value.menuVisible, animate = false)
}
@@ -384,6 +391,8 @@ class ReaderActivity : BaseActivity() {
binding.dialogRoot.setComposeContent {
val state by viewModel.state.collectAsState()
val settingsScreenModel = remember { ReaderSettingsScreenModel() }
val onDismissRequest = viewModel::closeDialog
when (state.dialog) {
is ReaderViewModel.Dialog.Loading -> {
@@ -401,14 +410,12 @@ class ReaderActivity : BaseActivity() {
},
)
}
is ReaderViewModel.Dialog.ColorFilter -> {
setMenuVisibility(false)
ReaderColorFilterDialog(
onDismissRequest = {
onDismissRequest()
setMenuVisibility(true)
},
readerPreferences = viewModel.readerPreferences,
is ReaderViewModel.Dialog.Settings -> {
ReaderSettingsDialog(
onDismissRequest = onDismissRequest,
onShowMenus = { setMenuVisibility(true) },
onHideMenus = { setMenuVisibility(false) },
screenModel = settingsScreenModel,
)
}
is ReaderViewModel.Dialog.PageActions -> {
@@ -541,7 +548,7 @@ class ReaderActivity : BaseActivity() {
}
// Settings sheet
with(binding.actionSettings) {
with(binding.actionSettingsLegacy) {
setTooltip(R.string.action_settings)
var readerSettingSheet: ReaderSettingsSheet? = null
@@ -551,13 +558,11 @@ class ReaderActivity : BaseActivity() {
readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() }
}
}
// Color filter sheet
with(binding.actionColorSettings) {
setTooltip(R.string.custom_filter)
with(binding.actionSettings) {
setTooltip(R.string.action_settings)
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
* [animate] the views.
*/
fun setMenuVisibility(visible: Boolean, animate: Boolean = true) {
private fun setMenuVisibility(visible: Boolean, animate: Boolean = true) {
viewModel.showMenus(visible)
if (visible) {
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
* bottom menu and delegates the change to the presenter.
*/
@SuppressLint("SetTextI18n")
fun onPageSelected(page: ReaderPage) {
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.
*/
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.
*/
fun updateViewerInset(fullscreen: Boolean) {
private fun updateViewerInset(fullscreen: Boolean) {
viewModel.state.value.viewer?.getView()?.applyInsetter {
if (!fullscreen) {
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.model.orientationType
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
import eu.kanade.domain.track.interactor.TrackChapter
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.download.DownloadManager
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.ImageSaver
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.online.HttpSource
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.ReaderChapter
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.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.system.isOnline
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -56,7 +51,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
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.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -96,12 +88,10 @@ class ReaderViewModel(
private val basePreferences: BasePreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = 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 getChapterByMangaId: GetChapterByMangaId = 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 updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
@@ -197,13 +187,8 @@ class ReaderViewModel(
.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 downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
init {
// To save state
@@ -257,8 +242,6 @@ class ReaderViewModel(
mutableState.update { it.copy(manga = manga) }
if (chapterId == -1L) chapterId = initialChapterId
checkTrackers(manga)
val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(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.
* 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
logcat { "Loading ${chapter.chapter.url}" }
viewModelScope.launchIO {
logcat { "Loading ${chapter.chapter.url}" }
flushReadTimer()
restartReadTimer()
withIOContext {
try {
loadChapter(loader, chapter)
} 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
* 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) {
return
}
@@ -395,9 +381,7 @@ class ReaderViewModel(
fun onViewerLoaded(viewer: Viewer?) {
mutableState.update {
it.copy(
viewer = viewer,
)
it.copy(viewer = viewer)
}
}
@@ -412,31 +396,19 @@ class ReaderViewModel(
return
}
val currentChapters = state.value.viewerChapters ?: return
val pages = page.chapter.pages ?: return
val selectedChapter = page.chapter
val pages = selectedChapter.pages ?: return
// Save last page read and mark as read if needed
saveReadingProgress()
mutableState.update {
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)
}
viewModelScope.launchNonCancellable {
updateChapterProgress(selectedChapter, page.index)
}
if (selectedChapter != currentChapters.currChapter) {
if (selectedChapter != getCurrentChapter()) {
logcat { "Setting ${selectedChapter.chapter.url} as active" }
setReadStartTime()
viewModelScope.launch { loadNewChapter(selectedChapter) }
loadNewChapter(selectedChapter)
}
val inDownloadRange = page.number.toDouble() / pages.size > 0.25
if (inDownloadRange) {
downloadNextChapters()
@@ -444,12 +416,11 @@ class ReaderViewModel(
}
private fun downloadNextChapters() {
if (downloadAheadAmount == 0) 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
if (getCurrentChapter()?.pageLoader?.isLocal == true) return
if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return
viewModelScope.launchIO {
@@ -466,7 +437,7 @@ class ReaderViewModel(
} else {
this
}
}.take(amount)
}.take(downloadAheadAmount)
downloadManager.downloadChapters(
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 {
viewModelScope.launchNonCancellable {
saveChapterProgress(it)
saveChapterHistory(it)
updateHistory(it)
}
}
}
/**
* Saves this [readerChapter] progress (last read page and whether it's read)
* if incognito mode isn't on.
* Saves the chapter last read history if incognito mode isn't on.
*/
private suspend fun saveChapterProgress(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) {
private suspend fun updateHistory(readerChapter: ReaderChapter) {
if (incognitoMode) return
val chapterId = readerChapter.chapter.id!!
@@ -551,17 +533,6 @@ class ReaderViewModel(
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.
*/
@@ -714,8 +685,8 @@ class ReaderViewModel(
mutableState.update { it.copy(dialog = Dialog.PageActions(page)) }
}
fun openColorFilterDialog() {
mutableState.update { it.copy(dialog = Dialog.ColorFilter) }
fun openSettingsDialog() {
mutableState.update { it.copy(dialog = Dialog.Settings) }
}
fun closeDialog() {
@@ -839,44 +810,14 @@ class ReaderViewModel(
* will run in a background thread and errors are ignored.
*/
private fun updateTrackChapterRead(readerChapter: ReaderChapter) {
if (incognitoMode || !hasTrackers) return
if (incognitoMode) return
if (!trackPreferences.autoUpdateTrack().get()) return
val manga = manga ?: return
val chapterRead = readerChapter.chapter.chapter_number.toDouble()
val trackManager = Injekt.get<TrackManager>()
val context = Injekt.get<Application>()
viewModelScope.launchNonCancellable {
getTracks.await(manga.id)
.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) }
trackChapter.await(context, manga.id, readerChapter.chapter.chapter_number.toDouble())
}
}
@@ -922,7 +863,7 @@ class ReaderViewModel(
sealed class Dialog {
object Loading : Dialog()
object ColorFilter : Dialog()
object Settings : 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
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
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.databinding.CommonTabbedSheetBinding
import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
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
class ReaderSettingsSheet(
private val activity: ReaderActivity,
) : BaseBottomSheetDialog(activity) {
) : BottomSheetDialog(activity) {
private val tabs = listOf(
ReaderReadingModeSettings(activity) to R.string.pref_category_reading_mode,
ReaderGeneralSettings(activity) to R.string.pref_category_general,
)
private val readerPreferences: ReaderPreferences by injectLazy()
private lateinit var binding: CommonTabbedSheetBinding
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
}
private lateinit var binding: ReaderReadingModeSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
behavior.isFitToContents = false
behavior.halfExpandedRatio = 0.25f
binding = ReaderReadingModeSettingsBinding.inflate(activity.layoutInflater)
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 {
return tabs[position].first
val mangaViewer = activity.viewModel.getMangaReadingMode()
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 {
return tabs.size
binding.rotationMode.onItemSelectedListener = { position ->
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 {
return activity.resources!!.getString(tabs[position].second)
}
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(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.Navigator
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.SettingsBackupAndSyncScreen
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.LocalBackPress
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" />
<ImageButton
android:id="@+id/action_settings"
android:id="@+id/action_settings_legacy"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_settings"
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_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_settings_24dp"
app:tint="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/action_color_settings"
android:id="@+id/action_settings"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/custom_filter"
android:contentDescription="@string/action_settings"
android:padding="@dimen/screen_edge_margin"
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:srcCompat="@drawable/ic_brightness_5_24dp"
app:srcCompat="@drawable/ic_settings_24dp"
app:tint="?attr/colorOnSurface" />
</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>
<dimen name="bottom_sheet_width">480dp</dimen>
<dimen name="screen_edge_margin">24dp</dimen>
</resources>

View File

@@ -9,20 +9,6 @@
<item>@string/vertical_plus_viewer</item>
</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">
<item>@string/scale_type_fit_screen</item>
<item>@string/scale_type_stretch</item>

View File

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