Add vertical chapter navigator for long strip mode (#3377)
This commit is contained in:
@@ -13,6 +13,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Add support for `tachiyomix` extension index format ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
- Add support for `tachiyomix` extension index format ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||||
|
- Add vertical chapter navigator for long strip mode ([@AntsyLich](https://github.com/AntsyLich)) ([#3377](https://github.com/mihonapp/mihon/pull/3377))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Change all reference of extension repo to extension store ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
- Change all reference of extension repo to extension store ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||||
|
|||||||
+12
-3
@@ -74,8 +74,8 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||||
val fullscreenPref = readerPreferences.fullscreen
|
val fullscreen by readerPreferences.fullscreen.collectAsState()
|
||||||
val fullscreen by fullscreenPref.collectAsState()
|
val verticalNavigatorForLongStrip by readerPreferences.verticalNavigatorForLongStrip.collectAsState()
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_category_display),
|
title = stringResource(MR.strings.pref_category_display),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
@@ -97,7 +97,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_reader_theme),
|
title = stringResource(MR.strings.pref_reader_theme),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = fullscreenPref,
|
preference = readerPreferences.fullscreen,
|
||||||
title = stringResource(MR.strings.pref_fullscreen),
|
title = stringResource(MR.strings.pref_fullscreen),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
@@ -113,6 +113,15 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
preference = readerPreferences.showPageNumber,
|
preference = readerPreferences.showPageNumber,
|
||||||
title = stringResource(MR.strings.pref_show_page_number),
|
title = stringResource(MR.strings.pref_show_page_number),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = readerPreferences.verticalNavigatorForLongStrip,
|
||||||
|
title = stringResource(MR.strings.pref_webtoon_vertical_navigator),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = readerPreferences.verticalNavigatorOnLeft,
|
||||||
|
title = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
|
||||||
|
enabled = verticalNavigatorForLongStrip,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@@ -21,14 +24,16 @@ import androidx.compose.foundation.layout.windowInsetsPadding
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.reader.components.ChapterNavigator
|
import eu.kanade.presentation.reader.components.ChapterNavigator
|
||||||
|
import eu.kanade.presentation.reader.components.ChapterNavigatorType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
|
private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
|
||||||
@@ -48,7 +53,7 @@ fun ReaderAppBars(
|
|||||||
onOpenInBrowser: (() -> Unit)?,
|
onOpenInBrowser: (() -> Unit)?,
|
||||||
onShare: (() -> Unit)?,
|
onShare: (() -> Unit)?,
|
||||||
|
|
||||||
viewer: Viewer?,
|
chapterNavigatorType: ChapterNavigatorType,
|
||||||
onNextChapter: () -> Unit,
|
onNextChapter: () -> Unit,
|
||||||
enabledNext: Boolean,
|
enabledNext: Boolean,
|
||||||
onPreviousChapter: () -> Unit,
|
onPreviousChapter: () -> Unit,
|
||||||
@@ -65,7 +70,6 @@ fun ReaderAppBars(
|
|||||||
onClickCropBorder: () -> Unit,
|
onClickCropBorder: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isRtl = viewer is R2LPagerViewer
|
|
||||||
val backgroundColor = MaterialTheme.colorScheme
|
val backgroundColor = MaterialTheme.colorScheme
|
||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||||
@@ -73,10 +77,8 @@ fun ReaderAppBars(
|
|||||||
Column(modifier = Modifier.fillMaxHeight()) {
|
Column(modifier = Modifier.fillMaxHeight()) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(initialOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
enter = slideInVertically(readerBarsSlideAnimationSpec) { -it } + fadeIn(readerBarsFadeAnimationSpec),
|
||||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
exit = slideOutVertically(readerBarsSlideAnimationSpec) { -it } + fadeOut(readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(targetOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
|
||||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
|
||||||
) {
|
) {
|
||||||
ReaderTopBar(
|
ReaderTopBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -93,18 +95,21 @@ fun ReaderAppBars(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
if (!chapterNavigatorType.isHorizontal()) {
|
||||||
|
val sliderOnLeft = chapterNavigatorType == ChapterNavigatorType.VERTICAL_LEFT
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalLayoutDirection provides if (sliderOnLeft) LayoutDirection.Ltr else LayoutDirection.Rtl,
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.weight(1f)) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(initialOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
enter = slideInHorizontally(readerBarsSlideAnimationSpec) { if (sliderOnLeft) -it else it } +
|
||||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
fadeIn(readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(targetOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
exit = slideOutHorizontally(readerBarsSlideAnimationSpec) { if (sliderOnLeft) -it else it } +
|
||||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
fadeOut(readerBarsFadeAnimationSpec),
|
||||||
) {
|
) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
|
|
||||||
ChapterNavigator(
|
ChapterNavigator(
|
||||||
isRtl = isRtl,
|
type = chapterNavigatorType,
|
||||||
onNextChapter = onNextChapter,
|
onNextChapter = onNextChapter,
|
||||||
enabledNext = enabledNext,
|
enabledNext = enabledNext,
|
||||||
onPreviousChapter = onPreviousChapter,
|
onPreviousChapter = onPreviousChapter,
|
||||||
@@ -113,6 +118,32 @@ fun ReaderAppBars(
|
|||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onPageIndexChange = onPageIndexChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = slideInVertically(readerBarsSlideAnimationSpec) { it } + fadeIn(readerBarsFadeAnimationSpec),
|
||||||
|
exit = slideOutVertically(readerBarsSlideAnimationSpec) { it } + fadeOut(readerBarsFadeAnimationSpec),
|
||||||
|
) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
|
||||||
|
if (chapterNavigatorType.isHorizontal()) {
|
||||||
|
ChapterNavigator(
|
||||||
|
type = chapterNavigatorType,
|
||||||
|
onNextChapter = onNextChapter,
|
||||||
|
enabledNext = enabledNext,
|
||||||
|
onPreviousChapter = onPreviousChapter,
|
||||||
|
enabledPrevious = enabledPrevious,
|
||||||
|
currentPage = currentPage,
|
||||||
|
totalPages = totalPages,
|
||||||
|
onPageIndexChange = onPageIndexChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
ReaderBottomBar(
|
ReaderBottomBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.interaction.collectIsDraggedAsState
|
import androidx.compose.foundation.interaction.collectIsDraggedAsState
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -15,9 +17,14 @@ import androidx.compose.material.icons.outlined.SkipNext
|
|||||||
import androidx.compose.material.icons.outlined.SkipPrevious
|
import androidx.compose.material.icons.outlined.SkipPrevious
|
||||||
import androidx.compose.material3.FilledIconButton
|
import androidx.compose.material3.FilledIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButtonColors
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalSlider
|
||||||
|
import androidx.compose.material3.rememberSliderState
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@@ -29,22 +36,34 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Slider
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
enum class ChapterNavigatorType {
|
||||||
|
HORIZONTAL_LTR,
|
||||||
|
HORIZONTAL_RTL,
|
||||||
|
VERTICAL_LEFT,
|
||||||
|
VERTICAL_RIGHT,
|
||||||
|
;
|
||||||
|
|
||||||
|
fun isHorizontal() = this in setOf(HORIZONTAL_LTR, HORIZONTAL_RTL)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterNavigator(
|
fun ChapterNavigator(
|
||||||
isRtl: Boolean,
|
type: ChapterNavigatorType,
|
||||||
onNextChapter: () -> Unit,
|
onNextChapter: () -> Unit,
|
||||||
enabledNext: Boolean,
|
enabledNext: Boolean,
|
||||||
onPreviousChapter: () -> Unit,
|
onPreviousChapter: () -> Unit,
|
||||||
@@ -53,11 +72,27 @@ fun ChapterNavigator(
|
|||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
onPageIndexChange: (Int) -> Unit,
|
onPageIndexChange: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isTabletUi = isTabletUi()
|
|
||||||
val horizontalPadding = if (isTabletUi) 24.dp else 8.dp
|
|
||||||
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
|
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val state = rememberSliderState(
|
||||||
|
value = currentPage.toFloat(),
|
||||||
|
steps = totalPages - 2,
|
||||||
|
valueRange = 1f..totalPages.toFloat(),
|
||||||
|
)
|
||||||
|
state.value = currentPage.toFloat()
|
||||||
|
state.onValueChange = { onPageIndexChange(it.roundToInt() - 1) }
|
||||||
|
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val sliderDragged by interactionSource.collectIsDraggedAsState()
|
||||||
|
LaunchedEffect(currentPage) {
|
||||||
|
if (sliderDragged) {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isTabletUi = isTabletUi()
|
||||||
|
val mainAxisPadding = if (isTabletUi) 24.dp else 8.dp
|
||||||
|
|
||||||
// Match with toolbar background color set in ReaderActivity
|
// Match with toolbar background color set in ReaderActivity
|
||||||
val backgroundColor = MaterialTheme.colorScheme
|
val backgroundColor = MaterialTheme.colorScheme
|
||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
@@ -67,12 +102,61 @@ fun ChapterNavigator(
|
|||||||
disabledContainerColor = backgroundColor,
|
disabledContainerColor = backgroundColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (type.isHorizontal()) {
|
||||||
|
ChapterNavigator(
|
||||||
|
isRtl = type == ChapterNavigatorType.HORIZONTAL_RTL,
|
||||||
|
state = state,
|
||||||
|
onNextChapter = onNextChapter,
|
||||||
|
enabledNext = enabledNext,
|
||||||
|
onPreviousChapter = onPreviousChapter,
|
||||||
|
enabledPrevious = enabledPrevious,
|
||||||
|
currentPage = currentPage,
|
||||||
|
totalPages = totalPages,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
mainAxisPadding = mainAxisPadding,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
buttonColor = buttonColor,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
VerticalChapterNavigator(
|
||||||
|
state = state,
|
||||||
|
onNextChapter = onNextChapter,
|
||||||
|
enabledNext = enabledNext,
|
||||||
|
onPreviousChapter = onPreviousChapter,
|
||||||
|
enabledPrevious = enabledPrevious,
|
||||||
|
currentPage = currentPage,
|
||||||
|
totalPages = totalPages,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
mainAxisPadding = mainAxisPadding,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
buttonColor = buttonColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChapterNavigator(
|
||||||
|
isRtl: Boolean,
|
||||||
|
state: SliderState,
|
||||||
|
onNextChapter: () -> Unit,
|
||||||
|
enabledNext: Boolean,
|
||||||
|
onPreviousChapter: () -> Unit,
|
||||||
|
enabledPrevious: Boolean,
|
||||||
|
currentPage: Int,
|
||||||
|
totalPages: Int,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
mainAxisPadding: Dp,
|
||||||
|
backgroundColor: Color,
|
||||||
|
buttonColor: IconButtonColors,
|
||||||
|
) {
|
||||||
|
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
|
||||||
|
|
||||||
// We explicitly handle direction based on the reader viewer rather than the system direction
|
// We explicitly handle direction based on the reader viewer rather than the system direction
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = horizontalPadding),
|
.padding(horizontal = mainAxisPadding),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
@@ -104,23 +188,11 @@ fun ChapterNavigator(
|
|||||||
Text(text = totalPages.toString(), color = Color.Transparent)
|
Text(text = totalPages.toString(), color = Color.Transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val sliderDragged by interactionSource.collectIsDraggedAsState()
|
|
||||||
LaunchedEffect(currentPage) {
|
|
||||||
if (sliderDragged) {
|
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Slider(
|
Slider(
|
||||||
|
state = state,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
value = currentPage,
|
|
||||||
valueRange = 1..totalPages,
|
|
||||||
onValueChange = f@{
|
|
||||||
if (it == currentPage) return@f
|
|
||||||
onPageIndexChange(it - 1)
|
|
||||||
},
|
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,13 +219,84 @@ fun ChapterNavigator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VerticalChapterNavigator(
|
||||||
|
state: SliderState,
|
||||||
|
onNextChapter: () -> Unit,
|
||||||
|
enabledNext: Boolean,
|
||||||
|
onPreviousChapter: () -> Unit,
|
||||||
|
enabledPrevious: Boolean,
|
||||||
|
currentPage: Int,
|
||||||
|
totalPages: Int,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
mainAxisPadding: Dp,
|
||||||
|
backgroundColor: Color,
|
||||||
|
buttonColor: IconButtonColors,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(vertical = mainAxisPadding),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
FilledIconButton(
|
||||||
|
enabled = enabledPrevious,
|
||||||
|
onClick = onPreviousChapter,
|
||||||
|
colors = buttonColor,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.SkipPrevious,
|
||||||
|
contentDescription = stringResource(MR.strings.action_previous_chapter),
|
||||||
|
modifier = Modifier.rotate(90f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPages > 1) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(24.dp))
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(text = currentPage.toString())
|
||||||
|
|
||||||
|
VerticalSlider(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = totalPages.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledIconButton(
|
||||||
|
enabled = enabledNext,
|
||||||
|
onClick = onNextChapter,
|
||||||
|
colors = buttonColor,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.SkipNext,
|
||||||
|
contentDescription = stringResource(MR.strings.action_next_chapter),
|
||||||
|
modifier = Modifier.rotate(90f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChapterNavigatorPreview() {
|
private fun ChapterNavigatorPreview() {
|
||||||
var currentPage by remember { mutableIntStateOf(1) }
|
var currentPage by remember { mutableIntStateOf(1) }
|
||||||
TachiyomiPreviewTheme {
|
TachiyomiPreviewTheme {
|
||||||
ChapterNavigator(
|
ChapterNavigator(
|
||||||
isRtl = false,
|
type = ChapterNavigatorType.VERTICAL_RIGHT,
|
||||||
onNextChapter = {},
|
onNextChapter = {},
|
||||||
enabledNext = true,
|
enabledNext = true,
|
||||||
onPreviousChapter = {},
|
onPreviousChapter = {},
|
||||||
|
|||||||
@@ -61,6 +61,19 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
pref = screenModel.preferences.showPageNumber,
|
pref = screenModel.preferences.showPageNumber,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val verticalNavigatorForLongStrip by screenModel.preferences.verticalNavigatorForLongStrip.collectAsState()
|
||||||
|
CheckboxItem(
|
||||||
|
label = stringResource(MR.strings.pref_webtoon_vertical_navigator),
|
||||||
|
pref = screenModel.preferences.verticalNavigatorForLongStrip,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (verticalNavigatorForLongStrip) {
|
||||||
|
CheckboxItem(
|
||||||
|
label = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
|
||||||
|
pref = screenModel.preferences.verticalNavigatorOnLeft,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_fullscreen),
|
label = stringResource(MR.strings.pref_fullscreen),
|
||||||
pref = screenModel.preferences.fullscreen,
|
pref = screenModel.preferences.fullscreen,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import eu.kanade.presentation.reader.ReaderPageActionsDialog
|
|||||||
import eu.kanade.presentation.reader.ReaderPageIndicator
|
import eu.kanade.presentation.reader.ReaderPageIndicator
|
||||||
import eu.kanade.presentation.reader.ReadingModeSelectDialog
|
import eu.kanade.presentation.reader.ReadingModeSelectDialog
|
||||||
import eu.kanade.presentation.reader.appbars.ReaderAppBars
|
import eu.kanade.presentation.reader.appbars.ReaderAppBars
|
||||||
|
import eu.kanade.presentation.reader.components.ChapterNavigatorType
|
||||||
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
||||||
@@ -76,6 +77,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
@@ -458,6 +460,9 @@ class ReaderActivity : BaseActivity() {
|
|||||||
val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode())
|
val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode())
|
||||||
val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon
|
val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon
|
||||||
|
|
||||||
|
val verticalNavigatorForLongStrip by readerPreferences.verticalNavigatorForLongStrip.collectAsState()
|
||||||
|
val verticalNavigatorOnLeft by readerPreferences.verticalNavigatorOnLeft.collectAsState()
|
||||||
|
|
||||||
ReaderAppBars(
|
ReaderAppBars(
|
||||||
visible = state.menuVisible,
|
visible = state.menuVisible,
|
||||||
|
|
||||||
@@ -471,7 +476,19 @@ class ReaderActivity : BaseActivity() {
|
|||||||
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
|
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
|
||||||
onShare = ::shareChapter.takeIf { isHttpSource },
|
onShare = ::shareChapter.takeIf { isHttpSource },
|
||||||
|
|
||||||
viewer = state.viewer,
|
chapterNavigatorType = if (isPagerType || !verticalNavigatorForLongStrip) {
|
||||||
|
if (state.viewer is R2LPagerViewer) {
|
||||||
|
ChapterNavigatorType.HORIZONTAL_RTL
|
||||||
|
} else {
|
||||||
|
ChapterNavigatorType.HORIZONTAL_LTR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (verticalNavigatorOnLeft) {
|
||||||
|
ChapterNavigatorType.VERTICAL_LEFT
|
||||||
|
} else {
|
||||||
|
ChapterNavigatorType.VERTICAL_RIGHT
|
||||||
|
}
|
||||||
|
},
|
||||||
onNextChapter = ::loadNextChapter,
|
onNextChapter = ::loadNextChapter,
|
||||||
enabledNext = state.viewerChapters?.nextChapter != null,
|
enabledNext = state.viewerChapters?.nextChapter != null,
|
||||||
onPreviousChapter = ::loadPreviousChapter,
|
onPreviousChapter = ::loadPreviousChapter,
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ class ReaderPreferences(
|
|||||||
|
|
||||||
val showPageNumber: Preference<Boolean> = preferenceStore.getBoolean("pref_show_page_number_key", true)
|
val showPageNumber: Preference<Boolean> = preferenceStore.getBoolean("pref_show_page_number_key", true)
|
||||||
|
|
||||||
|
val verticalNavigatorForLongStrip: Preference<Boolean> = preferenceStore.getBoolean(
|
||||||
|
"pref_webtoon_vertical_navigator",
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val verticalNavigatorOnLeft: Preference<Boolean> = preferenceStore.getBoolean(
|
||||||
|
"pref_webtoon_vertical_navigator_on_left",
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
val showReadingMode: Preference<Boolean> = preferenceStore.getBoolean("pref_show_reading_mode", true)
|
val showReadingMode: Preference<Boolean> = preferenceStore.getBoolean("pref_show_reading_mode", true)
|
||||||
|
|
||||||
val fullscreen: Preference<Boolean> = preferenceStore.getBoolean("fullscreen", true)
|
val fullscreen: Preference<Boolean> = preferenceStore.getBoolean("fullscreen", true)
|
||||||
|
|||||||
@@ -400,6 +400,8 @@
|
|||||||
<string name="pref_flash_style_white_black">White and Black</string>
|
<string name="pref_flash_style_white_black">White and Black</string>
|
||||||
<string name="pref_double_tap_anim_speed">Double tap animation speed</string>
|
<string name="pref_double_tap_anim_speed">Double tap animation speed</string>
|
||||||
<string name="pref_show_page_number">Show page number</string>
|
<string name="pref_show_page_number">Show page number</string>
|
||||||
|
<string name="pref_webtoon_vertical_navigator">Use vertical navigator for long strip reading</string>
|
||||||
|
<string name="pref_webtoon_vertical_navigator_on_left">Place vertical navigator on the left side</string>
|
||||||
<string name="pref_show_reading_mode">Show reading mode</string>
|
<string name="pref_show_reading_mode">Show reading mode</string>
|
||||||
<string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string>
|
<string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string>
|
||||||
<string name="pref_hardware_bitmap_threshold">Custom hardware bitmap threshold</string>
|
<string name="pref_hardware_bitmap_threshold">Custom hardware bitmap threshold</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user