Make reader edge-to-edge (mihonapp/mihon#1908)

(cherry picked from commit 5f0c4606681cd59b38ae0855c7827e149fa5488c)
This commit is contained in:
AntsyLich 2025-11-02 19:41:33 +06:00 committed by Cuong-Tran
parent 5aa67ebf34
commit 77ecb97535
12 changed files with 568 additions and 592 deletions

View file

@ -280,7 +280,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)

View file

@ -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
@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.Companion.zoomWid
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.WebtoonScaleType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.WebtoonScaleType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
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
@ -148,9 +148,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(),

View file

@ -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,11 +16,12 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.theme.TachiyomiPreviewTheme
@Composable @Composable
fun PageIndicatorText( fun ReaderPageIndicator(
// SY --> // SY -->
currentPage: String, currentPage: String,
// SY <-- // SY <--
totalPages: Int, totalPages: Int,
modifier: Modifier = Modifier,
) { ) {
if (currentPage.isEmpty() || totalPages <= 0) return if (currentPage.isEmpty() || totalPages <= 0) return
@ -40,6 +42,7 @@ fun PageIndicatorText(
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
modifier = modifier,
) { ) {
Text( Text(
text = text, text = text,
@ -54,10 +57,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)
} }
} }
} }

View file

@ -2,10 +2,13 @@ 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.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
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
@ -13,13 +16,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
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.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Bookmark
import androidx.compose.material.icons.outlined.BookmarkBorder
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
@ -30,20 +32,16 @@ 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.LayoutDirection
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.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
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)
// SY --> // SY -->
enum class NavBarType { enum class NavBarType {
@ -72,7 +70,6 @@ fun BoxIgnoreLayoutDirection(modifier: Modifier, content: @Composable BoxScope.(
@Composable @Composable
fun ReaderAppBars( fun ReaderAppBars(
visible: Boolean, visible: Boolean,
fullscreen: Boolean,
mangaTitle: String?, mangaTitle: String?,
chapterTitle: String?, chapterTitle: String?,
@ -129,28 +126,18 @@ 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) {
Modifier.systemBarsPadding()
} else {
Modifier
}
// SY --> // SY -->
BoxIgnoreLayoutDirection( BoxIgnoreLayoutDirection(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = visible && navBarType == NavBarType.VerticalLeft, visible = visible && navBarType == NavBarType.VerticalLeft,
enter = slideInHorizontally( enter = slideInHorizontally(initialOffsetX = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
initialOffsetX = { -it }, fadeIn(animationSpec = readerBarsFadeAnimationSpec),
animationSpec = animationSpec, exit = slideOutHorizontally(targetOffsetX = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
), fadeOut(animationSpec = readerBarsFadeAnimationSpec),
exit = slideOutHorizontally( modifier = Modifier
targetOffsetX = { -it }, .padding(top = 164.dp, bottom = 70.dp)
animationSpec = animationSpec,
),
modifier = modifierWithInsetsPadding
.padding(bottom = 64.dp, top = 112.dp)
.align(Alignment.TopStart), .align(Alignment.TopStart),
) { ) {
ChapterNavigator( ChapterNavigator(
@ -162,23 +149,21 @@ fun ReaderAppBars(
currentPage = currentPage, currentPage = currentPage,
totalPages = totalPages, totalPages = totalPages,
onPageIndexChange = onPageIndexChange, onPageIndexChange = onPageIndexChange,
// SY -->
isVerticalSlider = true, isVerticalSlider = true,
currentPageText = currentPageText, currentPageText = currentPageText,
// SY <--
) )
} }
AnimatedVisibility( AnimatedVisibility(
visible = visible && navBarType == NavBarType.VerticalRight, visible = visible && navBarType == NavBarType.VerticalRight,
enter = slideInHorizontally( enter = slideInHorizontally(initialOffsetX = { it }, animationSpec = readerBarsSlideAnimationSpec) +
initialOffsetX = { it }, fadeIn(animationSpec = readerBarsFadeAnimationSpec),
animationSpec = animationSpec, exit = slideOutHorizontally(targetOffsetX = { it }, animationSpec = readerBarsSlideAnimationSpec) +
), fadeOut(animationSpec = readerBarsFadeAnimationSpec),
exit = slideOutHorizontally( modifier = Modifier
targetOffsetX = { it }, .padding(top = 164.dp, bottom = 70.dp)
animationSpec = animationSpec,
),
modifier = modifierWithInsetsPadding
.padding(bottom = 64.dp, top = 112.dp)
.align(Alignment.TopEnd), .align(Alignment.TopEnd),
) { ) {
ChapterNavigator( ChapterNavigator(
@ -190,87 +175,38 @@ fun ReaderAppBars(
currentPage = currentPage, currentPage = currentPage,
totalPages = totalPages, totalPages = totalPages,
onPageIndexChange = onPageIndexChange, onPageIndexChange = onPageIndexChange,
// SY -->
isVerticalSlider = true, isVerticalSlider = true,
currentPageText = currentPageText, currentPageText = currentPageText,
// SY <--
) )
} }
// SY <-- // SY <--
Column( Column(modifier = Modifier.fillMaxHeight()) {
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,
),
) { ) {
// SY --> // SY -->
Column(modifierWithInsetsPadding) { Column {
// SY <-- // SY <--
AppBar( ReaderTopBar(
modifier = /*SY --> */ Modifier /*SY <-- */ 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() // SY -->
.apply { onOpenInWebView = null, // onOpenInWebView,
add( onOpenInBrowser = null, // onOpenInBrowser,
AppBar.Action( onShare = null, // onShare,
title = stringResource( // SY <--
if (bookmarked) {
MR.strings.action_remove_bookmark
} else {
MR.strings.action_bookmark
},
),
icon = if (bookmarked) {
Icons.Outlined.Bookmark
} else {
Icons.Outlined.BookmarkBorder
},
onClick = onToggleBookmarked,
),
)
/* SY -->
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,
),
)
}
SY <-- */
}
.build(),
)
},
) )
// SY --> // SY -->
ExhUtils( ExhUtils(
@ -288,28 +224,23 @@ fun ReaderAppBars(
onClickBoostPage = onClickBoostPage, onClickBoostPage = onClickBoostPage,
onClickBoostPageHelp = onClickBoostPageHelp, onClickBoostPageHelp = onClickBoostPageHelp,
) )
// SY <--
} }
// SY <--
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
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(
modifier = modifierWithInsetsPadding,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
// SY -->
if (navBarType == NavBarType.Bottom) { if (navBarType == NavBarType.Bottom) {
// SY <--
ChapterNavigator( ChapterNavigator(
isRtl = isRtl, isRtl = isRtl,
onNextChapter = onNextChapter, onNextChapter = onNextChapter,
@ -319,15 +250,18 @@ fun ReaderAppBars(
currentPage = currentPage, currentPage = currentPage,
totalPages = totalPages, totalPages = totalPages,
onPageIndexChange = onPageIndexChange, onPageIndexChange = onPageIndexChange,
// SY -->
isVerticalSlider = false, isVerticalSlider = false,
currentPageText = currentPageText, currentPageText = currentPageText,
// SY <--
) )
} }
BottomReaderBar( ReaderBottomBar(
// SY --> modifier = Modifier
enabledButtons = enabledButtons, .fillMaxWidth()
// SY <-- .background(backgroundColor)
backgroundColor = backgroundColor, .padding(horizontal = MaterialTheme.padding.small)
.windowInsetsPadding(WindowInsets.navigationBars),
readingMode = readingMode, readingMode = readingMode,
onClickReadingMode = onClickReadingMode, onClickReadingMode = onClickReadingMode,
orientation = orientation, orientation = orientation,
@ -336,6 +270,7 @@ fun ReaderAppBars(
onClickCropBorder = onClickCropBorder, onClickCropBorder = onClickCropBorder,
onClickSettings = onClickSettings, onClickSettings = onClickSettings,
// SY --> // SY -->
enabledButtons = enabledButtons,
currentReadingMode = currentReadingMode, currentReadingMode = currentReadingMode,
dualPageSplitEnabled = dualPageSplitEnabled, dualPageSplitEnabled = dualPageSplitEnabled,
doublePages = doublePages, doublePages = doublePages,

View file

@ -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.Explore import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.FormatListNumbered import androidx.compose.material.icons.outlined.FormatListNumbered
@ -17,9 +14,8 @@ import androidx.compose.material3.MaterialTheme
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.ReaderBottomButton import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
@ -30,11 +26,10 @@ import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
fun BottomReaderBar( fun ReaderBottomBar(
// SY --> // SY -->
enabledButtons: ImmutableSet<String>, enabledButtons: ImmutableSet<String>,
// SY <-- // SY <--
backgroundColor: Color,
readingMode: ReadingMode, readingMode: ReadingMode,
onClickReadingMode: () -> Unit, onClickReadingMode: () -> Unit,
orientation: ReaderOrientation, orientation: ReaderOrientation,
@ -53,15 +48,14 @@ fun BottomReaderBar(
onClickPageLayout: () -> Unit, onClickPageLayout: () -> Unit,
onClickShiftPage: () -> Unit, onClickShiftPage: () -> Unit,
// SY <-- // SY <--
modifier: Modifier = Modifier,
) { ) {
// KMK --> // KMK -->
val iconColor = MaterialTheme.colorScheme.primary val iconColor = MaterialTheme.colorScheme.primary
// KMK <-- // KMK <--
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,
) { ) {

View file

@ -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(),
)
},
)
}

View file

@ -5,8 +5,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.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
@ -84,7 +86,8 @@ internal fun 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(),

View file

@ -22,17 +22,21 @@ 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.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
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.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -40,14 +44,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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
@ -55,12 +61,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
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 com.materialkolor.dynamicColorScheme
import com.materialkolor.dynamiccolor.ColorSpec
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.domain.connections.service.ConnectionsPreferences import eu.kanade.domain.connections.service.ConnectionsPreferences
@ -69,9 +71,9 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.reader.ChapterListDialog import eu.kanade.presentation.reader.ChapterListDialog
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.NavBarType import eu.kanade.presentation.reader.appbars.NavBarType
import eu.kanade.presentation.reader.appbars.ReaderAppBars import eu.kanade.presentation.reader.appbars.ReaderAppBars
@ -171,10 +173,7 @@ class ReaderActivity : BaseActivity() {
private val preferences = Injekt.get<BasePreferences>() private val preferences = Injekt.get<BasePreferences>()
// KMK --> // KMK -->
private val uiPreferences = Injekt.get<UiPreferences>() val themeCoverBased = Injekt.get<UiPreferences>().themeCoverBased().get()
private val themeCoverBased = uiPreferences.themeCoverBased().get()
private val themeDarkAmoled = uiPreferences.themeDarkAmoled().get()
private val themeCoverBasedStyle = uiPreferences.themeCoverBasedStyle().get()
// KMK <-- // KMK <--
// AM (CONNECTIONS) --> // AM (CONNECTIONS) -->
@ -186,8 +185,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() }
// SY --> // SY -->
private val sourceManager = Injekt.get<SourceManager>() private val sourceManager = Injekt.get<SourceManager>()
// SY <-- // SY <--
@ -201,7 +198,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
@ -224,10 +221,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
@ -253,7 +257,11 @@ class ReaderActivity : BaseActivity() {
} }
config = ReaderConfig() config = ReaderConfig()
initializeMenu() setMenuVisibility(viewModel.state.value.menuVisible)
// EXH -->
enableExhAutoScroll()
// EXH <--
// Finish when incognito mode is disabled // Finish when incognito mode is disabled
preferences.incognitoMode().changes() preferences.incognitoMode().changes()
@ -310,6 +318,210 @@ class ReaderActivity : BaseActivity() {
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
} }
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
// KMK -->
TachiyomiTheme(
seedColor = seedColorState().takeIf { themeCoverBased },
typography = MaterialTheme.typography.copy(
bodyLarge = MaterialTheme.typography.bodySmall,
),
) {
val context = LocalContext.current
// KMK <--
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(
// SY -->
currentPage = state.currentPageText,
// SY <--
totalPages = state.totalPages,
modifier = Modifier
.align(Alignment.BottomCenter)
.then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()),
)
}
ContentOverlay(state = state)
AppBars(state = state)
}
// KMK -->
val externalStoragePermissionNotGranted = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_DENIED
val permissionRequester = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = {
toast(KMR.strings.permission_writing_external_storage_succeed)
},
)
// KMK <--
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(
// KMK -->
color = MaterialTheme.colorScheme.primary,
// KMK <--
)
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,
// SY -->
onSave = { extra ->
// KMK -->
if (externalStoragePermissionNotGranted) {
permissionRequester.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
// KMK <--
viewModel.saveImage(extra)
}
},
onShareCombined = viewModel::shareImages,
onSaveCombined = {
// KMK -->
if (externalStoragePermissionNotGranted) {
permissionRequester.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
// KMK <--
viewModel.saveImages()
}
},
hasExtraPage = (state.dialog as? ReaderViewModel.Dialog.PageActions)?.extraPage != null,
)
}
is ReaderViewModel.Dialog.ChapterList -> {
var chapters by remember {
mutableStateOf(viewModel.getChapters().toImmutableList())
}
ChapterListDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
chapters = chapters,
onClickChapter = {
viewModel.loadNewChapterFromDialog(it)
onDismissRequest()
},
onBookmark = { chapter ->
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
chapters = chapters.map {
if (it.chapter.id == chapter.id) {
it.copy(chapter = chapter.copy(bookmark = !chapter.bookmark))
} else {
it
}
}.toImmutableList()
},
state.dateRelativeTime,
// KMK -->
onDownloadAction = { chapter, action ->
viewModel.handleDownloadAction(chapter, action)
},
// KMK <--
)
}
ReaderViewModel.Dialog.AutoScrollHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_autoscroll_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_autoscroll_help_message)) },
)
ReaderViewModel.Dialog.BoostPageHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_boost_page_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_boost_page_help_message)) },
)
ReaderViewModel.Dialog.RetryAllHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_retry_all_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_retry_all_help_message)) },
)
// SY <--
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.
*/ */
@ -411,56 +623,8 @@ class ReaderActivity : BaseActivity() {
return handled || super.dispatchGenericMotionEvent(event) return handled || super.dispatchGenericMotionEvent(event)
} }
/**
* Initializes the reader menu. It sets up click listeners and the initial visibility.
*/
private fun initializeMenu() {
// KMK -->
@Composable @Composable
fun pageNumberContent() { private fun ContentOverlay(state: ReaderViewModel.State) {
// KMK <--
val state by viewModel.state.collectAsState()
val showPageNumber by viewModel.readerPreferences.showPageNumber().collectAsState()
if (!state.menuVisible && showPageNumber) {
PageIndicatorText(
// SY -->
currentPage = state.currentPageText,
// SY <--
totalPages = state.totalPages,
)
}
}
// KMK -->
binding.pageNumber.setComposeContent {
TachiyomiTheme(
seedColor = seedColorState().takeIf { themeCoverBased },
) {
pageNumberContent()
}
}
@Composable
fun dialogRootContent() {
val context = LocalContext.current
// KMK <--
val state by viewModel.state.collectAsState()
val settingsScreenModel = remember {
ReaderSettingsScreenModel(
readerState = viewModel.state,
hasDisplayCutout = hasCutout,
onChangeReadingMode = viewModel::setMangaReadingMode,
onChangeOrientation = viewModel::setMangaOrientationType,
)
}
if (!ifSourcesLoaded()) {
return
}
val isHttpSource = viewModel.getSource() is HttpSource
val isFullscreen by readerPreferences.fullscreen().collectAsState()
val flashOnPageChange by readerPreferences.flashOnPageChange().collectAsState() val flashOnPageChange by readerPreferences.flashOnPageChange().collectAsState()
val colorOverlayEnabled by readerPreferences.colorFilter().collectAsState() val colorOverlayEnabled by readerPreferences.colorFilter().collectAsState()
@ -470,6 +634,25 @@ class ReaderActivity : BaseActivity() {
ReaderPreferences.ColorFilterMode.getOrNull(colorOverlayMode)?.second ReaderPreferences.ColorFilterMode.getOrNull(colorOverlayMode)?.second
} }
ReaderContentOverlay(
brightness = state.brightnessOverlayValue,
color = colorOverlay.takeIf { colorOverlayEnabled },
colorBlendMode = colorOverlayBlendMode,
)
if (flashOnPageChange) {
DisplayRefreshHost(hostState = displayRefreshHost)
}
}
@Composable
fun AppBars(state: ReaderViewModel.State) {
if (!ifSourcesLoaded()) {
return
}
val isHttpSource = viewModel.getSource() is HttpSource
val cropBorderPaged by readerPreferences.cropBorders().collectAsState() val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState() val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
// SY --> // SY -->
@ -505,27 +688,8 @@ class ReaderActivity : BaseActivity() {
} }
// SY <-- // SY <--
// KMK -->
val externalStoragePermissionNotGranted = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_DENIED
val permissionRequester = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = {
toast(KMR.strings.permission_writing_external_storage_succeed)
},
)
// KMK <--
ReaderContentOverlay(
brightness = state.brightnessOverlayValue,
color = colorOverlay.takeIf { colorOverlayEnabled },
colorBlendMode = colorOverlayBlendMode,
)
ReaderAppBars( ReaderAppBars(
visible = state.menuVisible, visible = state.menuVisible,
fullscreen = isFullscreen,
mangaTitle = state.manga?.title, mangaTitle = state.manga?.title,
chapterTitle = state.currentChapter?.chapter?.name, chapterTitle = state.currentChapter?.chapter?.name,
@ -599,201 +763,6 @@ class ReaderActivity : BaseActivity() {
onClickShiftPage = ::shiftDoublePages, onClickShiftPage = ::shiftDoublePages,
// SY <-- // SY <--
) )
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(
// KMK -->
color = MaterialTheme.colorScheme.primary,
// KMK <--
)
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 = { extra ->
// KMK -->
if (externalStoragePermissionNotGranted) {
permissionRequester.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
// KMK <--
viewModel.saveImage(extra)
}
},
onShareCombined = viewModel::shareImages,
onSaveCombined = {
// KMK -->
if (externalStoragePermissionNotGranted) {
permissionRequester.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
// KMK <--
viewModel.saveImages()
}
},
hasExtraPage = (state.dialog as? ReaderViewModel.Dialog.PageActions)?.extraPage != null,
)
}
is ReaderViewModel.Dialog.ChapterList -> {
var chapters by remember {
mutableStateOf(viewModel.getChapters().toImmutableList())
}
ChapterListDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
chapters = chapters,
onClickChapter = {
viewModel.loadNewChapterFromDialog(it)
onDismissRequest()
},
onBookmark = { chapter ->
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
chapters = chapters.map {
if (it.chapter.id == chapter.id) {
it.copy(chapter = chapter.copy(bookmark = !chapter.bookmark))
} else {
it
}
}.toImmutableList()
},
state.dateRelativeTime,
// KMK -->
onDownloadAction = { chapter, action ->
viewModel.handleDownloadAction(chapter, action)
},
// KMK <--
)
}
// SY -->
ReaderViewModel.Dialog.AutoScrollHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_autoscroll_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_autoscroll_help_message)) },
)
ReaderViewModel.Dialog.BoostPageHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_boost_page_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_boost_page_help_message)) },
)
ReaderViewModel.Dialog.RetryAllHelp -> AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_ok))
}
},
title = { Text(text = stringResource(SYMR.strings.eh_retry_all_help)) },
text = { Text(text = stringResource(SYMR.strings.eh_retry_all_help_message)) },
)
// SY <--
null -> {}
}
}
// KMK -->
binding.dialogRoot.setComposeContent {
TachiyomiTheme(
seedColor = seedColorState().takeIf { themeCoverBased },
typography = MaterialTheme.typography.copy(
bodyLarge = MaterialTheme.typography.bodySmall,
),
) {
dialogRootContent()
}
}
val colorScheme = seedColorStatic()?.let {
dynamicColorScheme(
seedColor = it,
isDark = isNightMode(),
isAmoled = themeDarkAmoled,
style = themeCoverBasedStyle,
specVersion = ColorSpec.SpecVersion.SPEC_2025,
)
}
// KMK <--
val toolbarColor = ColorUtils.setAlphaComponent(
// KMK -->
if (themeCoverBased && colorScheme != null) {
colorScheme.surfaceColorAtElevation(3.dp).toArgb()
} else {
// KMK <--
SurfaceColors.SURFACE_2.getColor(this)
},
if (isNightMode()) 230 else 242, // 90% dark 95% light
)
@Suppress("DEPRECATION")
window.statusBarColor = toolbarColor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@Suppress("DEPRECATION")
window.navigationBarColor = toolbarColor
}
// Set initial visibility
setMenuVisibility(viewModel.state.value.menuVisible)
enableExhAutoScroll()
} }
// KMK --> // KMK -->
@ -812,6 +781,7 @@ class ReaderActivity : BaseActivity() {
} }
// KMK <-- // KMK <--
// EXH -->
private fun enableExhAutoScroll() { private fun enableExhAutoScroll() {
readerPreferences.autoscrollInterval().changes() readerPreferences.autoscrollInterval().changes()
.combine(viewModel.state.map { it.autoScroll }.distinctUntilChanged()) { interval, enabled -> .combine(viewModel.state.map { it.autoScroll }.distinctUntilChanged()) { interval, enabled ->
@ -965,13 +935,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 {
if (readerPreferences.fullscreen().get()) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
} }
} }
@ -1189,7 +1154,6 @@ class ReaderActivity : BaseActivity() {
* Called from the viewer whenever a [page] is marked as active. It updates the values of the * Called from the viewer whenever a [page] is marked as active. It updates the values of the
* bottom menu and delegates the change to the presenter. * bottom menu and delegates the change to the presenter.
*/ */
@SuppressLint("SetTextI18n")
fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) { fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) {
// SY --> // SY -->
val currentPageText = if (hasExtraPage) { val currentPageText = if (hasExtraPage) {
@ -1204,18 +1168,16 @@ class ReaderActivity : BaseActivity() {
} else { } else {
"${page.number}" "${page.number}"
} }
viewModel.onPageSelected(page, currentPageText, hasExtraPage)
// SY <-- // SY <--
viewModel.onPageSelected(page, /* SY --> */ currentPageText, hasExtraPage /* SY <-- */)
} }
/** /**
* Called from the viewer whenever a [page] is long clicked. A bottom sheet with a list of * Called from the viewer whenever a [page] is long clicked. A bottom sheet with a list of
* actions to perform is shown. * actions to perform is shown.
*/ */
fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) { fun onPageLongTap(page: ReaderPage, /* SY --> */ extraPage: ReaderPage? = null /* SY <-- */) {
// SY --> viewModel.openPageDialog(page, /* SY --> */ extraPage /* SY <-- */)
viewModel.openPageDialog(page, extraPage)
// SY <--
} }
/** /**
@ -1260,8 +1222,10 @@ class ReaderActivity : BaseActivity() {
val manga = viewModel.manga ?: return val manga = viewModel.manga ?: return
val chapter = page.chapter.chapter val chapter = page.chapter.chapter
// SY --> val intent = uri.toShareIntent(
val text = if (secondPage != null) { context = applicationContext,
message = // SY -->
if (secondPage != null) {
stringResource( stringResource(
SYMR.strings.share_pages_info, SYMR.strings.share_pages_info,
manga.title, manga.title,
@ -1275,13 +1239,9 @@ class ReaderActivity : BaseActivity() {
}, },
) )
} else { } else {
stringResource(MR.strings.share_page_info, manga.title, chapter.name, page.number)
}
// SY <-- // SY <--
stringResource(MR.strings.share_page_info, manga.title, chapter.name, page.number)
val intent = uri.toShareIntent( },
context = applicationContext,
message = /* SY --> */ text, // SY <--
) )
startActivity(Intent.createChooser(intent, stringResource(MR.strings.action_share))) startActivity(Intent.createChooser(intent, stringResource(MR.strings.action_share)))
} }
@ -1335,13 +1295,22 @@ 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)
} }
/** /**
@ -1483,15 +1452,13 @@ 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
@SuppressLint("NewApi")
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)
} }
/** /**
@ -1546,6 +1513,7 @@ class ReaderActivity : BaseActivity() {
} }
} }
// KMK -->
/** /**
* Updates the Discord Rich Presence (RPC) status based on the current reader activity. * Updates the Discord Rich Presence (RPC) status based on the current reader activity.
* *
@ -1587,4 +1555,5 @@ class ReaderActivity : BaseActivity() {
} }
} }
} }
// KMK <--
} }

View file

@ -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(),

View file

@ -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
} }
/** /**

View file

@ -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" />

View file

@ -60,7 +60,6 @@ material = "com.google.android.material:material:1.13.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"