mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-09 02:28:55 +01:00
Make reader edge-to-edge (#1908)
This commit is contained in:
@@ -17,6 +17,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Increased default concurrent page downloads to 5 ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637))
|
- Increased default concurrent page downloads to 5 ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637))
|
||||||
|
- Hide "Show content in cutout area" reader setting on Android 15+ as it's not supported ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
- Spoofing of `X-Requested-With` header to support newer WebView versions ([@Guzmazow](https://github.com/Guzmazow)) ([#2491](https://github.com/mihonapp/mihon/pull/2491))
|
- Spoofing of `X-Requested-With` header to support newer WebView versions ([@Guzmazow](https://github.com/Guzmazow)) ([#2491](https://github.com/mihonapp/mihon/pull/2491))
|
||||||
@@ -37,6 +38,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
- Fix scrollbar not showing when animator duration scale animation is turned off ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2398](https://github.com/mihonapp/mihon/pull/2398))
|
- Fix scrollbar not showing when animator duration scale animation is turned off ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2398](https://github.com/mihonapp/mihon/pull/2398))
|
||||||
- Fix date picker not allowing the same start and finish date in negative time zones ([@AntsyLich](https://github.com/AntsyLich), [@kashish-aggarwal21](https://github.com/kashish-aggarwal21)) ([#2617](https://github.com/mihonapp/mihon/pull/2617))
|
- Fix date picker not allowing the same start and finish date in negative time zones ([@AntsyLich](https://github.com/AntsyLich), [@kashish-aggarwal21](https://github.com/kashish-aggarwal21)) ([#2617](https://github.com/mihonapp/mihon/pull/2617))
|
||||||
- Fix reader tap zones triggering after scrolling was stopped by the user ([@Naputt1](https://github.com/Naputt1), [@AntsyLich](https://github.com/AntsyLich)) ([#2518](https://github.com/mihonapp/mihon/pull/2518))
|
- Fix reader tap zones triggering after scrolling was stopped by the user ([@Naputt1](https://github.com/Naputt1), [@AntsyLich](https://github.com/AntsyLich)) ([#2518](https://github.com/mihonapp/mihon/pull/2518))
|
||||||
|
- Fix reader page indicator being partially visible on some devices ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||||
|
- Fix inconsistent system bar and reader app bar background ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||||
|
- Fix transparent system bar background in reader on Android 15+ ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
- Delegate Suwayomi tracker authentication to extension ([@cpiber](https://github.com/cpiber)) ([#2476](https://github.com/mihonapp/mihon/pull/2476))
|
- Delegate Suwayomi tracker authentication to extension ([@cpiber](https://github.com/cpiber)) ([#2476](https://github.com/mihonapp/mihon/pull/2476))
|
||||||
|
|||||||
@@ -261,7 +261,6 @@ dependencies {
|
|||||||
implementation(libs.directionalviewpager) {
|
implementation(libs.directionalviewpager) {
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
|
||||||
implementation(libs.richeditor.compose)
|
implementation(libs.richeditor.compose)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -10,6 +9,7 @@ import eu.kanade.presentation.more.settings.Preference
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
@@ -103,9 +103,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.cutoutShort(),
|
preference = readerPreferences.cutoutShort(),
|
||||||
title = stringResource(MR.strings.pref_cutout_short),
|
title = stringResource(MR.strings.pref_cutout_short),
|
||||||
enabled = fullscreen &&
|
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
||||||
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.keepScreenOn(),
|
preference = readerPreferences.keepScreenOn(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -15,9 +16,10 @@ import androidx.compose.ui.unit.sp
|
|||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PageIndicatorText(
|
fun ReaderPageIndicator(
|
||||||
currentPage: Int,
|
currentPage: Int,
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (currentPage <= 0 || totalPages <= 0) return
|
if (currentPage <= 0 || totalPages <= 0) return
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ fun PageIndicatorText(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
@@ -50,10 +53,10 @@ fun PageIndicatorText(
|
|||||||
|
|
||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun PageIndicatorTextPreview() {
|
private fun ReaderPageIndicatorPreview() {
|
||||||
TachiyomiPreviewTheme {
|
TachiyomiPreviewTheme {
|
||||||
Surface {
|
Surface {
|
||||||
PageIndicatorText(currentPage = 10, totalPages = 69)
|
ReaderPageIndicator(currentPage = 10, totalPages = 69)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,42 +2,41 @@ package eu.kanade.presentation.reader.appbars
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
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.fadeOut
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
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.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.material.icons.outlined.Bookmark
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
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.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
|
||||||
import eu.kanade.presentation.reader.components.ChapterNavigator
|
import eu.kanade.presentation.reader.components.ChapterNavigator
|
||||||
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.Viewer
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
|
||||||
|
|
||||||
private val animationSpec = tween<IntOffset>(200)
|
private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
|
||||||
|
private val readerBarsFadeAnimationSpec = tween<Float>(150)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderAppBars(
|
fun ReaderAppBars(
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
fullscreen: Boolean,
|
|
||||||
|
|
||||||
mangaTitle: String?,
|
mangaTitle: String?,
|
||||||
chapterTitle: String?,
|
chapterTitle: String?,
|
||||||
@@ -71,83 +70,26 @@ fun ReaderAppBars(
|
|||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||||
|
|
||||||
val modifierWithInsetsPadding = if (fullscreen) {
|
Column(modifier = Modifier.fillMaxHeight()) {
|
||||||
Modifier.systemBarsPadding()
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(
|
enter = slideInVertically(initialOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||||
initialOffsetY = { -it },
|
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
animationSpec = animationSpec,
|
exit = slideOutVertically(targetOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||||
),
|
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(
|
|
||||||
targetOffsetY = { -it },
|
|
||||||
animationSpec = animationSpec,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
AppBar(
|
ReaderTopBar(
|
||||||
modifier = modifierWithInsetsPadding
|
modifier = Modifier
|
||||||
|
.background(backgroundColor)
|
||||||
.clickable(onClick = onClickTopAppBar),
|
.clickable(onClick = onClickTopAppBar),
|
||||||
backgroundColor = backgroundColor,
|
mangaTitle = mangaTitle,
|
||||||
title = mangaTitle,
|
chapterTitle = chapterTitle,
|
||||||
subtitle = chapterTitle,
|
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
bookmarked = bookmarked,
|
||||||
AppBarActions(
|
onToggleBookmarked = onToggleBookmarked,
|
||||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
onOpenInWebView = onOpenInWebView,
|
||||||
.apply {
|
onOpenInBrowser = onOpenInBrowser,
|
||||||
add(
|
onShare = onShare,
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(
|
|
||||||
if (bookmarked) {
|
|
||||||
MR.strings.action_remove_bookmark
|
|
||||||
} else {
|
|
||||||
MR.strings.action_bookmark
|
|
||||||
},
|
|
||||||
),
|
|
||||||
icon = if (bookmarked) {
|
|
||||||
Icons.Outlined.Bookmark
|
|
||||||
} else {
|
|
||||||
Icons.Outlined.BookmarkBorder
|
|
||||||
},
|
|
||||||
onClick = onToggleBookmarked,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
onOpenInWebView?.let {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_open_in_web_view),
|
|
||||||
onClick = it,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onOpenInBrowser?.let {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_open_in_browser),
|
|
||||||
onClick = it,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onShare?.let {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_share),
|
|
||||||
onClick = it,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,19 +97,12 @@ fun ReaderAppBars(
|
|||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(
|
enter = slideInVertically(initialOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||||
initialOffsetY = { it },
|
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
animationSpec = animationSpec,
|
exit = slideOutVertically(targetOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||||
),
|
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(
|
|
||||||
targetOffsetY = { it },
|
|
||||||
animationSpec = animationSpec,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
|
||||||
modifier = modifierWithInsetsPadding,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
||||||
) {
|
|
||||||
ChapterNavigator(
|
ChapterNavigator(
|
||||||
isRtl = isRtl,
|
isRtl = isRtl,
|
||||||
onNextChapter = onNextChapter,
|
onNextChapter = onNextChapter,
|
||||||
@@ -178,8 +113,12 @@ fun ReaderAppBars(
|
|||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onPageIndexChange = onPageIndexChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
)
|
)
|
||||||
BottomReaderBar(
|
ReaderBottomBar(
|
||||||
backgroundColor = backgroundColor,
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small)
|
||||||
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
readingMode = readingMode,
|
readingMode = readingMode,
|
||||||
onClickReadingMode = onClickReadingMode,
|
onClickReadingMode = onClickReadingMode,
|
||||||
orientation = orientation,
|
orientation = orientation,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package eu.kanade.presentation.reader.appbars
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -12,9 +9,8 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
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
|
||||||
@@ -22,8 +18,7 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomReaderBar(
|
fun ReaderBottomBar(
|
||||||
backgroundColor: Color,
|
|
||||||
readingMode: ReadingMode,
|
readingMode: ReadingMode,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
orientation: ReaderOrientation,
|
orientation: ReaderOrientation,
|
||||||
@@ -31,12 +26,11 @@ fun BottomReaderBar(
|
|||||||
cropEnabled: Boolean,
|
cropEnabled: Boolean,
|
||||||
onClickCropBorder: () -> Unit,
|
onClickCropBorder: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.pointerInput(Unit) {},
|
||||||
.background(backgroundColor)
|
|
||||||
.padding(8.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Bookmark
|
||||||
|
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderTopBar(
|
||||||
|
mangaTitle: String?,
|
||||||
|
chapterTitle: String?,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
bookmarked: Boolean,
|
||||||
|
onToggleBookmarked: () -> Unit,
|
||||||
|
onOpenInWebView: (() -> Unit)?,
|
||||||
|
onOpenInBrowser: (() -> Unit)?,
|
||||||
|
onShare: (() -> Unit)?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
modifier = modifier,
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
title = mangaTitle,
|
||||||
|
subtitle = chapterTitle,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
|
add(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(
|
||||||
|
if (bookmarked) {
|
||||||
|
MR.strings.action_remove_bookmark
|
||||||
|
} else {
|
||||||
|
MR.strings.action_bookmark
|
||||||
|
},
|
||||||
|
),
|
||||||
|
icon = if (bookmarked) {
|
||||||
|
Icons.Outlined.Bookmark
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.BookmarkBorder
|
||||||
|
},
|
||||||
|
onClick = onToggleBookmarked,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onOpenInWebView?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_open_in_web_view),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onOpenInBrowser?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_open_in_browser),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShare?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_share),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,8 +6,10 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
@@ -64,7 +66,8 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
pref = screenModel.preferences.fullscreen(),
|
pref = screenModel.preferences.fullscreen(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (screenModel.hasDisplayCutout && screenModel.preferences.fullscreen().get()) {
|
val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
|
||||||
|
if (LocalView.current.hasDisplayCutout() && isFullscreen) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_cutout_short),
|
label = stringResource(MR.strings.pref_cutout_short),
|
||||||
pref = screenModel.preferences.cutoutShort(),
|
pref = screenModel.preferences.cutoutShort(),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.app.assist.AssistContent
|
import android.app.assist.AssistContent
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
@@ -16,40 +15,47 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
import android.view.View.LAYER_TYPE_HARDWARE
|
import android.view.View.LAYER_TYPE_HARDWARE
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.transition.doOnEnd
|
import androidx.core.transition.doOnEnd
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
|
||||||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.core.util.ifSourcesLoaded
|
import eu.kanade.core.util.ifSourcesLoaded
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.presentation.reader.DisplayRefreshHost
|
import eu.kanade.presentation.reader.DisplayRefreshHost
|
||||||
import eu.kanade.presentation.reader.OrientationSelectDialog
|
import eu.kanade.presentation.reader.OrientationSelectDialog
|
||||||
import eu.kanade.presentation.reader.PageIndicatorText
|
|
||||||
import eu.kanade.presentation.reader.ReaderContentOverlay
|
import eu.kanade.presentation.reader.ReaderContentOverlay
|
||||||
import eu.kanade.presentation.reader.ReaderPageActionsDialog
|
import eu.kanade.presentation.reader.ReaderPageActionsDialog
|
||||||
|
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.settings.ReaderSettingsDialog
|
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
||||||
@@ -121,8 +127,6 @@ class ReaderActivity : BaseActivity() {
|
|||||||
val viewModel by viewModels<ReaderViewModel>()
|
val viewModel by viewModels<ReaderViewModel>()
|
||||||
private var assistUrl: String? = null
|
private var assistUrl: String? = null
|
||||||
|
|
||||||
private val hasCutout by lazy { hasDisplayCutout() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration at reader level, like background color or forced orientation.
|
* Configuration at reader level, like background color or forced orientation.
|
||||||
*/
|
*/
|
||||||
@@ -132,7 +136,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
private var readingModeToast: Toast? = null
|
private var readingModeToast: Toast? = null
|
||||||
private val displayRefreshHost = DisplayRefreshHost()
|
private val displayRefreshHost = DisplayRefreshHost()
|
||||||
|
|
||||||
private val windowInsetsController by lazy { WindowInsetsControllerCompat(window, binding.root) }
|
private val windowInsetsController by lazy { WindowInsetsControllerCompat(window, window.decorView) }
|
||||||
|
|
||||||
private var loadingIndicator: ReaderProgressIndicator? = null
|
private var loadingIndicator: ReaderProgressIndicator? = null
|
||||||
|
|
||||||
@@ -146,7 +150,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
registerSecureActivity(this)
|
registerSecureActivity(this)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
overrideActivityTransition(
|
overrideActivityTransition(
|
||||||
Activity.OVERRIDE_TRANSITION_OPEN,
|
OVERRIDE_TRANSITION_OPEN,
|
||||||
R.anim.shared_axis_x_push_enter,
|
R.anim.shared_axis_x_push_enter,
|
||||||
R.anim.shared_axis_x_push_exit,
|
R.anim.shared_axis_x_push_exit,
|
||||||
)
|
)
|
||||||
@@ -155,10 +159,17 @@ class ReaderActivity : BaseActivity() {
|
|||||||
overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit)
|
overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT))
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
window.isNavigationBarContrastEnforced = false
|
||||||
|
}
|
||||||
|
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
binding.setComposeOverlay()
|
||||||
|
|
||||||
if (viewModel.needsInit()) {
|
if (viewModel.needsInit()) {
|
||||||
val manga = intent.extras?.getLong("manga", -1) ?: -1L
|
val manga = intent.extras?.getLong("manga", -1) ?: -1L
|
||||||
@@ -181,7 +192,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config = ReaderConfig()
|
config = ReaderConfig()
|
||||||
initializeMenu()
|
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||||
|
|
||||||
// Finish when incognito mode is disabled
|
// Finish when incognito mode is disabled
|
||||||
preferences.incognitoMode().changes()
|
preferences.incognitoMode().changes()
|
||||||
@@ -238,6 +249,93 @@ class ReaderActivity : BaseActivity() {
|
|||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val showPageNumber by readerPreferences.showPageNumber().collectAsState()
|
||||||
|
val isFullscreen by readerPreferences.fullscreen().collectAsState()
|
||||||
|
val settingsScreenModel = remember {
|
||||||
|
ReaderSettingsScreenModel(
|
||||||
|
readerState = viewModel.state,
|
||||||
|
onChangeReadingMode = viewModel::setMangaReadingMode,
|
||||||
|
onChangeOrientation = viewModel::setMangaOrientationType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
if (!state.menuVisible && showPageNumber) {
|
||||||
|
ReaderPageIndicator(
|
||||||
|
currentPage = state.currentPage,
|
||||||
|
totalPages = state.totalPages,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentOverlay(state = state)
|
||||||
|
|
||||||
|
AppBars(state = state)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onDismissRequest = viewModel::closeDialog
|
||||||
|
when (state.dialog) {
|
||||||
|
is ReaderViewModel.Dialog.Loading -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
confirmButton = {},
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
Text(stringResource(MR.strings.loading))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ReaderViewModel.Dialog.Settings -> {
|
||||||
|
ReaderSettingsDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onShowMenus = { setMenuVisibility(true) },
|
||||||
|
onHideMenus = { setMenuVisibility(false) },
|
||||||
|
screenModel = settingsScreenModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ReaderViewModel.Dialog.ReadingModeSelect -> {
|
||||||
|
ReadingModeSelectDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
screenModel = settingsScreenModel,
|
||||||
|
onChange = { stringRes ->
|
||||||
|
menuToggleToast?.cancel()
|
||||||
|
if (!readerPreferences.showReadingMode().get()) {
|
||||||
|
menuToggleToast = toast(stringRes)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ReaderViewModel.Dialog.OrientationModeSelect -> {
|
||||||
|
OrientationSelectDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
screenModel = settingsScreenModel,
|
||||||
|
onChange = { stringRes ->
|
||||||
|
menuToggleToast?.cancel()
|
||||||
|
menuToggleToast = toast(stringRes)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ReaderViewModel.Dialog.PageActions -> {
|
||||||
|
ReaderPageActionsDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onSetAsCover = viewModel::setAsCover,
|
||||||
|
onShare = viewModel::shareImage,
|
||||||
|
onSave = viewModel::saveImage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is destroyed. Cleans up the viewer, configuration and any view.
|
* Called when the activity is destroyed. Cleans up the viewer, configuration and any view.
|
||||||
*/
|
*/
|
||||||
@@ -289,7 +387,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
super.finish()
|
super.finish()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
overrideActivityTransition(
|
overrideActivityTransition(
|
||||||
Activity.OVERRIDE_TRANSITION_CLOSE,
|
OVERRIDE_TRANSITION_CLOSE,
|
||||||
R.anim.shared_axis_x_pop_enter,
|
R.anim.shared_axis_x_pop_enter,
|
||||||
R.anim.shared_axis_x_pop_exit,
|
R.anim.shared_axis_x_pop_exit,
|
||||||
)
|
)
|
||||||
@@ -327,180 +425,82 @@ class ReaderActivity : BaseActivity() {
|
|||||||
return handled || super.dispatchGenericMotionEvent(event)
|
return handled || super.dispatchGenericMotionEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Composable
|
||||||
* Initializes the reader menu. It sets up click listeners and the initial visibility.
|
private fun ContentOverlay(state: ReaderViewModel.State) {
|
||||||
*/
|
val flashOnPageChange by readerPreferences.flashOnPageChange().collectAsState()
|
||||||
private fun initializeMenu() {
|
|
||||||
binding.pageNumber.setComposeContent {
|
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val showPageNumber by viewModel.readerPreferences.showPageNumber().collectAsState()
|
|
||||||
|
|
||||||
if (!state.menuVisible && showPageNumber) {
|
val colorOverlayEnabled by readerPreferences.colorFilter().collectAsState()
|
||||||
PageIndicatorText(
|
val colorOverlay by readerPreferences.colorFilterValue().collectAsState()
|
||||||
currentPage = state.currentPage,
|
val colorOverlayMode by readerPreferences.colorFilterMode().collectAsState()
|
||||||
totalPages = state.totalPages,
|
val colorOverlayBlendMode = remember(colorOverlayMode) {
|
||||||
)
|
ReaderPreferences.ColorFilterMode.getOrNull(colorOverlayMode)?.second
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.dialogRoot.setComposeContent {
|
ReaderContentOverlay(
|
||||||
val state by viewModel.state.collectAsState()
|
brightness = state.brightnessOverlayValue,
|
||||||
val settingsScreenModel = remember {
|
color = colorOverlay.takeIf { colorOverlayEnabled },
|
||||||
ReaderSettingsScreenModel(
|
colorBlendMode = colorOverlayBlendMode,
|
||||||
readerState = viewModel.state,
|
|
||||||
hasDisplayCutout = hasCutout,
|
|
||||||
onChangeReadingMode = viewModel::setMangaReadingMode,
|
|
||||||
onChangeOrientation = viewModel::setMangaOrientationType,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ifSourcesLoaded()) {
|
|
||||||
return@setComposeContent
|
|
||||||
}
|
|
||||||
|
|
||||||
val isHttpSource = viewModel.getSource() is HttpSource
|
|
||||||
val isFullscreen by readerPreferences.fullscreen().collectAsState()
|
|
||||||
val flashOnPageChange by readerPreferences.flashOnPageChange().collectAsState()
|
|
||||||
|
|
||||||
val colorOverlayEnabled by readerPreferences.colorFilter().collectAsState()
|
|
||||||
val colorOverlay by readerPreferences.colorFilterValue().collectAsState()
|
|
||||||
val colorOverlayMode by readerPreferences.colorFilterMode().collectAsState()
|
|
||||||
val colorOverlayBlendMode = remember(colorOverlayMode) {
|
|
||||||
ReaderPreferences.ColorFilterMode.getOrNull(colorOverlayMode)?.second
|
|
||||||
}
|
|
||||||
|
|
||||||
val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
|
|
||||||
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
|
|
||||||
val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode())
|
|
||||||
val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon
|
|
||||||
|
|
||||||
ReaderContentOverlay(
|
|
||||||
brightness = state.brightnessOverlayValue,
|
|
||||||
color = colorOverlay.takeIf { colorOverlayEnabled },
|
|
||||||
colorBlendMode = colorOverlayBlendMode,
|
|
||||||
)
|
|
||||||
|
|
||||||
ReaderAppBars(
|
|
||||||
visible = state.menuVisible,
|
|
||||||
fullscreen = isFullscreen,
|
|
||||||
|
|
||||||
mangaTitle = state.manga?.title,
|
|
||||||
chapterTitle = state.currentChapter?.chapter?.name,
|
|
||||||
navigateUp = onBackPressedDispatcher::onBackPressed,
|
|
||||||
onClickTopAppBar = ::openMangaScreen,
|
|
||||||
bookmarked = state.bookmarked,
|
|
||||||
onToggleBookmarked = viewModel::toggleChapterBookmark,
|
|
||||||
onOpenInWebView = ::openChapterInWebView.takeIf { isHttpSource },
|
|
||||||
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
|
|
||||||
onShare = ::shareChapter.takeIf { isHttpSource },
|
|
||||||
|
|
||||||
viewer = state.viewer,
|
|
||||||
onNextChapter = ::loadNextChapter,
|
|
||||||
enabledNext = state.viewerChapters?.nextChapter != null,
|
|
||||||
onPreviousChapter = ::loadPreviousChapter,
|
|
||||||
enabledPrevious = state.viewerChapters?.prevChapter != null,
|
|
||||||
currentPage = state.currentPage,
|
|
||||||
totalPages = state.totalPages,
|
|
||||||
onPageIndexChange = {
|
|
||||||
isScrollingThroughPages = true
|
|
||||||
moveToPageIndex(it)
|
|
||||||
},
|
|
||||||
|
|
||||||
readingMode = ReadingMode.fromPreference(
|
|
||||||
viewModel.getMangaReadingMode(resolveDefault = false),
|
|
||||||
),
|
|
||||||
onClickReadingMode = viewModel::openReadingModeSelectDialog,
|
|
||||||
orientation = ReaderOrientation.fromPreference(
|
|
||||||
viewModel.getMangaOrientation(resolveDefault = false),
|
|
||||||
),
|
|
||||||
onClickOrientation = viewModel::openOrientationModeSelectDialog,
|
|
||||||
cropEnabled = cropEnabled,
|
|
||||||
onClickCropBorder = {
|
|
||||||
val enabled = viewModel.toggleCropBorders()
|
|
||||||
menuToggleToast?.cancel()
|
|
||||||
menuToggleToast = toast(if (enabled) MR.strings.on else MR.strings.off)
|
|
||||||
},
|
|
||||||
onClickSettings = viewModel::openSettingsDialog,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (flashOnPageChange) {
|
|
||||||
DisplayRefreshHost(
|
|
||||||
hostState = displayRefreshHost,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val onDismissRequest = viewModel::closeDialog
|
|
||||||
when (state.dialog) {
|
|
||||||
is ReaderViewModel.Dialog.Loading -> {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = {},
|
|
||||||
confirmButton = {},
|
|
||||||
text = {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
Text(stringResource(MR.strings.loading))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ReaderViewModel.Dialog.Settings -> {
|
|
||||||
ReaderSettingsDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onShowMenus = { setMenuVisibility(true) },
|
|
||||||
onHideMenus = { setMenuVisibility(false) },
|
|
||||||
screenModel = settingsScreenModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ReaderViewModel.Dialog.ReadingModeSelect -> {
|
|
||||||
ReadingModeSelectDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
screenModel = settingsScreenModel,
|
|
||||||
onChange = { stringRes ->
|
|
||||||
menuToggleToast?.cancel()
|
|
||||||
if (!readerPreferences.showReadingMode().get()) {
|
|
||||||
menuToggleToast = toast(stringRes)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ReaderViewModel.Dialog.OrientationModeSelect -> {
|
|
||||||
OrientationSelectDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
screenModel = settingsScreenModel,
|
|
||||||
onChange = { stringRes ->
|
|
||||||
menuToggleToast?.cancel()
|
|
||||||
menuToggleToast = toast(stringRes)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ReaderViewModel.Dialog.PageActions -> {
|
|
||||||
ReaderPageActionsDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onSetAsCover = viewModel::setAsCover,
|
|
||||||
onShare = viewModel::shareImage,
|
|
||||||
onSave = viewModel::saveImage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val toolbarColor = ColorUtils.setAlphaComponent(
|
|
||||||
SurfaceColors.SURFACE_2.getColor(this),
|
|
||||||
if (isNightMode()) 230 else 242, // 90% dark 95% light
|
|
||||||
)
|
)
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
window.statusBarColor = toolbarColor
|
if (flashOnPageChange) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
DisplayRefreshHost(hostState = displayRefreshHost)
|
||||||
@Suppress("DEPRECATION")
|
}
|
||||||
window.navigationBarColor = toolbarColor
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppBars(state: ReaderViewModel.State) {
|
||||||
|
if (!ifSourcesLoaded()) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial visibility
|
val isHttpSource = viewModel.getSource() is HttpSource
|
||||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
|
||||||
|
val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
|
||||||
|
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
|
||||||
|
val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode())
|
||||||
|
val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon
|
||||||
|
|
||||||
|
ReaderAppBars(
|
||||||
|
visible = state.menuVisible,
|
||||||
|
|
||||||
|
mangaTitle = state.manga?.title,
|
||||||
|
chapterTitle = state.currentChapter?.chapter?.name,
|
||||||
|
navigateUp = onBackPressedDispatcher::onBackPressed,
|
||||||
|
onClickTopAppBar = ::openMangaScreen,
|
||||||
|
bookmarked = state.bookmarked,
|
||||||
|
onToggleBookmarked = viewModel::toggleChapterBookmark,
|
||||||
|
onOpenInWebView = ::openChapterInWebView.takeIf { isHttpSource },
|
||||||
|
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
|
||||||
|
onShare = ::shareChapter.takeIf { isHttpSource },
|
||||||
|
|
||||||
|
viewer = state.viewer,
|
||||||
|
onNextChapter = ::loadNextChapter,
|
||||||
|
enabledNext = state.viewerChapters?.nextChapter != null,
|
||||||
|
onPreviousChapter = ::loadPreviousChapter,
|
||||||
|
enabledPrevious = state.viewerChapters?.prevChapter != null,
|
||||||
|
currentPage = state.currentPage,
|
||||||
|
totalPages = state.totalPages,
|
||||||
|
onPageIndexChange = {
|
||||||
|
isScrollingThroughPages = true
|
||||||
|
moveToPageIndex(it)
|
||||||
|
},
|
||||||
|
|
||||||
|
readingMode = ReadingMode.fromPreference(
|
||||||
|
viewModel.getMangaReadingMode(resolveDefault = false),
|
||||||
|
),
|
||||||
|
onClickReadingMode = viewModel::openReadingModeSelectDialog,
|
||||||
|
orientation = ReaderOrientation.fromPreference(
|
||||||
|
viewModel.getMangaOrientation(resolveDefault = false),
|
||||||
|
),
|
||||||
|
onClickOrientation = viewModel::openOrientationModeSelectDialog,
|
||||||
|
cropEnabled = cropEnabled,
|
||||||
|
onClickCropBorder = {
|
||||||
|
val enabled = viewModel.toggleCropBorders()
|
||||||
|
menuToggleToast?.cancel()
|
||||||
|
menuToggleToast = toast(if (enabled) MR.strings.on else MR.strings.off)
|
||||||
|
},
|
||||||
|
onClickSettings = viewModel::openSettingsDialog,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -510,13 +510,8 @@ class ReaderActivity : BaseActivity() {
|
|||||||
viewModel.showMenus(visible)
|
viewModel.showMenus(visible)
|
||||||
if (visible) {
|
if (visible) {
|
||||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
} else if (readerPreferences.fullscreen().get()) {
|
||||||
} else {
|
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
if (readerPreferences.fullscreen().get()) {
|
|
||||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
|
||||||
windowInsetsController.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,7 +588,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
try {
|
try {
|
||||||
readingModeToast?.cancel()
|
readingModeToast?.cancel()
|
||||||
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
|
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||||
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
|
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,15 +781,24 @@ class ReaderActivity : BaseActivity() {
|
|||||||
* Updates viewer inset depending on fullscreen reader preferences.
|
* Updates viewer inset depending on fullscreen reader preferences.
|
||||||
*/
|
*/
|
||||||
private fun updateViewerInset(fullscreen: Boolean) {
|
private fun updateViewerInset(fullscreen: Boolean) {
|
||||||
viewModel.state.value.viewer?.getView()?.applyInsetter {
|
val view = viewModel.state.value.viewer?.getView() ?: return
|
||||||
if (!fullscreen) {
|
|
||||||
type(navigationBars = true, statusBars = true) {
|
view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen)
|
||||||
padding()
|
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
|
||||||
}
|
view.applyInsetsPadding(windowInsets, fullscreen)
|
||||||
}
|
windowInsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun View.applyInsetsPadding(windowInsets: WindowInsetsCompat?, fullscreen: Boolean) {
|
||||||
|
val insets = if (!fullscreen) {
|
||||||
|
windowInsets?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: Insets.NONE
|
||||||
|
} else {
|
||||||
|
Insets.NONE
|
||||||
|
}
|
||||||
|
setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that handles the user preferences of the reader.
|
* Class that handles the user preferences of the reader.
|
||||||
*/
|
*/
|
||||||
@@ -902,15 +906,12 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setCutoutShort(enabled: Boolean) {
|
private fun setCutoutShort(enabled: Boolean) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
|
if (!window.decorView.hasDisplayCutout()) return
|
||||||
|
|
||||||
window.attributes.layoutInDisplayCutoutMode = when (enabled) {
|
window.attributes.layoutInDisplayCutoutMode = when (enabled) {
|
||||||
true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
false -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
false -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger relayout
|
|
||||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import uy.kohesive.injekt.api.get
|
|||||||
|
|
||||||
class ReaderSettingsScreenModel(
|
class ReaderSettingsScreenModel(
|
||||||
readerState: StateFlow<ReaderViewModel.State>,
|
readerState: StateFlow<ReaderViewModel.State>,
|
||||||
val hasDisplayCutout: Boolean,
|
|
||||||
val onChangeReadingMode: (ReadingMode) -> Unit,
|
val onChangeReadingMode: (ReadingMode) -> Unit,
|
||||||
val onChangeOrientation: (ReaderOrientation) -> Unit,
|
val onChangeOrientation: (ReaderOrientation) -> Unit,
|
||||||
val preferences: ReaderPreferences = Injekt.get(),
|
val preferences: ReaderPreferences = Injekt.get(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.util.system
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -57,11 +57,11 @@ fun Context.isNightMode(): Boolean {
|
|||||||
/**
|
/**
|
||||||
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
|
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
|
||||||
*
|
*
|
||||||
* Only works in Android 9+.
|
* Only relevant from Android 9 to Android 14.
|
||||||
*/
|
*/
|
||||||
fun Activity.hasDisplayCutout(): Boolean {
|
fun View.hasDisplayCutout(): Boolean {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
return Build.VERSION.SDK_INT in Build.VERSION_CODES.P..Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||||
window.decorView.rootWindowInsets?.displayCutout != null
|
rootWindowInsets?.displayCutout != null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,12 +13,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:descendantFocusability="blocksDescendants" />
|
android:descendantFocusability="blocksDescendants" />
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
|
||||||
android:id="@+id/page_number"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|center_horizontal" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.ui.reader.ReaderNavigationOverlayView
|
<eu.kanade.tachiyomi.ui.reader.ReaderNavigationOverlayView
|
||||||
@@ -30,7 +24,7 @@
|
|||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
<androidx.compose.ui.platform.ComposeView
|
||||||
android:id="@+id/dialog_root"
|
android:id="@+id/compose_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ material = "com.google.android.material:material:1.12.0"
|
|||||||
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
||||||
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
||||||
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
||||||
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
|
||||||
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.1"
|
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.1"
|
||||||
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
|
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
|
||||||
compose-grid = "io.woong.compose.grid:grid:1.2.2"
|
compose-grid = "io.woong.compose.grid:grid:1.2.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user