mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-06 17:18:56 +01:00
Make reader edge-to-edge (#1908)
This commit is contained in:
@@ -261,7 +261,6 @@ dependencies {
|
||||
implementation(libs.directionalviewpager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
}
|
||||
implementation(libs.insetter)
|
||||
implementation(libs.richeditor.compose)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.bundles.voyager)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.presentation.more.settings.screen
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
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.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
@@ -103,9 +103,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.cutoutShort(),
|
||||
title = stringResource(MR.strings.pref_cutout_short),
|
||||
enabled = fullscreen &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
|
||||
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.keepScreenOn(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -15,9 +16,10 @@ import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
|
||||
@Composable
|
||||
fun PageIndicatorText(
|
||||
fun ReaderPageIndicator(
|
||||
currentPage: Int,
|
||||
totalPages: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (currentPage <= 0 || totalPages <= 0) return
|
||||
|
||||
@@ -36,6 +38,7 @@ fun PageIndicatorText(
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
@@ -50,10 +53,10 @@ fun PageIndicatorText(
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun PageIndicatorTextPreview() {
|
||||
private fun ReaderPageIndicatorPreview() {
|
||||
TachiyomiPreviewTheme {
|
||||
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.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Bookmark
|
||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
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.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
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
|
||||
fun ReaderAppBars(
|
||||
visible: Boolean,
|
||||
fullscreen: Boolean,
|
||||
|
||||
mangaTitle: String?,
|
||||
chapterTitle: String?,
|
||||
@@ -71,83 +70,26 @@ fun ReaderAppBars(
|
||||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
|
||||
val modifierWithInsetsPadding = if (fullscreen) {
|
||||
Modifier.systemBarsPadding()
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxHeight()) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { -it },
|
||||
animationSpec = animationSpec,
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
targetOffsetY = { -it },
|
||||
animationSpec = animationSpec,
|
||||
),
|
||||
enter = slideInVertically(initialOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(targetOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||
) {
|
||||
AppBar(
|
||||
modifier = modifierWithInsetsPadding
|
||||
ReaderTopBar(
|
||||
modifier = Modifier
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = onClickTopAppBar),
|
||||
backgroundColor = backgroundColor,
|
||||
title = mangaTitle,
|
||||
subtitle = chapterTitle,
|
||||
mangaTitle = mangaTitle,
|
||||
chapterTitle = 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(),
|
||||
)
|
||||
},
|
||||
bookmarked = bookmarked,
|
||||
onToggleBookmarked = onToggleBookmarked,
|
||||
onOpenInWebView = onOpenInWebView,
|
||||
onOpenInBrowser = onOpenInBrowser,
|
||||
onShare = onShare,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -155,19 +97,12 @@ fun ReaderAppBars(
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { it },
|
||||
animationSpec = animationSpec,
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
targetOffsetY = { it },
|
||||
animationSpec = animationSpec,
|
||||
),
|
||||
enter = slideInVertically(initialOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(targetOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||
) {
|
||||
Column(
|
||||
modifier = modifierWithInsetsPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
|
||||
ChapterNavigator(
|
||||
isRtl = isRtl,
|
||||
onNextChapter = onNextChapter,
|
||||
@@ -178,8 +113,12 @@ fun ReaderAppBars(
|
||||
totalPages = totalPages,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
)
|
||||
BottomReaderBar(
|
||||
backgroundColor = backgroundColor,
|
||||
ReaderBottomBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor)
|
||||
.padding(horizontal = MaterialTheme.padding.small)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
readingMode = readingMode,
|
||||
onClickReadingMode = onClickReadingMode,
|
||||
orientation = orientation,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package eu.kanade.presentation.reader.appbars
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -12,9 +9,8 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
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.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
@@ -22,8 +18,7 @@ import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun BottomReaderBar(
|
||||
backgroundColor: Color,
|
||||
fun ReaderBottomBar(
|
||||
readingMode: ReadingMode,
|
||||
onClickReadingMode: () -> Unit,
|
||||
orientation: ReaderOrientation,
|
||||
@@ -31,12 +26,11 @@ fun BottomReaderBar(
|
||||
cropEnabled: Boolean,
|
||||
onClickCropBorder: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor)
|
||||
.padding(8.dp),
|
||||
modifier = modifier
|
||||
.pointerInput(Unit) {},
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
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.runtime.Composable
|
||||
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.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||
@@ -64,7 +66,8 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||
pref = screenModel.preferences.fullscreen(),
|
||||
)
|
||||
|
||||
if (screenModel.hasDisplayCutout && screenModel.preferences.fullscreen().get()) {
|
||||
val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
|
||||
if (LocalView.current.hasDisplayCutout() && isFullscreen) {
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.pref_cutout_short),
|
||||
pref = screenModel.preferences.cutoutShort(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.assist.AssistContent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@@ -16,40 +15,47 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.View.LAYER_TYPE_HARDWARE
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.transition.doOnEnd
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||
import com.hippo.unifile.UniFile
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.core.util.ifSourcesLoaded
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.presentation.reader.DisplayRefreshHost
|
||||
import eu.kanade.presentation.reader.OrientationSelectDialog
|
||||
import eu.kanade.presentation.reader.PageIndicatorText
|
||||
import eu.kanade.presentation.reader.ReaderContentOverlay
|
||||
import eu.kanade.presentation.reader.ReaderPageActionsDialog
|
||||
import eu.kanade.presentation.reader.ReaderPageIndicator
|
||||
import eu.kanade.presentation.reader.ReadingModeSelectDialog
|
||||
import eu.kanade.presentation.reader.appbars.ReaderAppBars
|
||||
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
||||
@@ -121,8 +127,6 @@ class ReaderActivity : BaseActivity() {
|
||||
val viewModel by viewModels<ReaderViewModel>()
|
||||
private var assistUrl: String? = null
|
||||
|
||||
private val hasCutout by lazy { hasDisplayCutout() }
|
||||
|
||||
/**
|
||||
* Configuration at reader level, like background color or forced orientation.
|
||||
*/
|
||||
@@ -132,7 +136,7 @@ class ReaderActivity : BaseActivity() {
|
||||
private var readingModeToast: Toast? = null
|
||||
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
|
||||
|
||||
@@ -146,7 +150,7 @@ class ReaderActivity : BaseActivity() {
|
||||
registerSecureActivity(this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
overrideActivityTransition(
|
||||
Activity.OVERRIDE_TRANSITION_OPEN,
|
||||
OVERRIDE_TRANSITION_OPEN,
|
||||
R.anim.shared_axis_x_push_enter,
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
binding.setComposeOverlay()
|
||||
|
||||
if (viewModel.needsInit()) {
|
||||
val manga = intent.extras?.getLong("manga", -1) ?: -1L
|
||||
@@ -181,7 +192,7 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
config = ReaderConfig()
|
||||
initializeMenu()
|
||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||
|
||||
// Finish when incognito mode is disabled
|
||||
preferences.incognitoMode().changes()
|
||||
@@ -238,6 +249,93 @@ class ReaderActivity : BaseActivity() {
|
||||
.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.
|
||||
*/
|
||||
@@ -289,7 +387,7 @@ class ReaderActivity : BaseActivity() {
|
||||
super.finish()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
overrideActivityTransition(
|
||||
Activity.OVERRIDE_TRANSITION_CLOSE,
|
||||
OVERRIDE_TRANSITION_CLOSE,
|
||||
R.anim.shared_axis_x_pop_enter,
|
||||
R.anim.shared_axis_x_pop_exit,
|
||||
)
|
||||
@@ -327,180 +425,82 @@ class ReaderActivity : BaseActivity() {
|
||||
return handled || super.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the reader menu. It sets up click listeners and the initial visibility.
|
||||
*/
|
||||
private fun initializeMenu() {
|
||||
binding.pageNumber.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val showPageNumber by viewModel.readerPreferences.showPageNumber().collectAsState()
|
||||
@Composable
|
||||
private fun ContentOverlay(state: ReaderViewModel.State) {
|
||||
val flashOnPageChange by readerPreferences.flashOnPageChange().collectAsState()
|
||||
|
||||
if (!state.menuVisible && showPageNumber) {
|
||||
PageIndicatorText(
|
||||
currentPage = state.currentPage,
|
||||
totalPages = state.totalPages,
|
||||
)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
binding.dialogRoot.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val settingsScreenModel = remember {
|
||||
ReaderSettingsScreenModel(
|
||||
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
|
||||
ReaderContentOverlay(
|
||||
brightness = state.brightnessOverlayValue,
|
||||
color = colorOverlay.takeIf { colorOverlayEnabled },
|
||||
colorBlendMode = colorOverlayBlendMode,
|
||||
)
|
||||
@Suppress("DEPRECATION")
|
||||
window.statusBarColor = toolbarColor
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
@Suppress("DEPRECATION")
|
||||
window.navigationBarColor = toolbarColor
|
||||
|
||||
if (flashOnPageChange) {
|
||||
DisplayRefreshHost(hostState = displayRefreshHost)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppBars(state: ReaderViewModel.State) {
|
||||
if (!ifSourcesLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set initial visibility
|
||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||
val isHttpSource = viewModel.getSource() is HttpSource
|
||||
|
||||
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)
|
||||
if (visible) {
|
||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
} else {
|
||||
if (readerPreferences.fullscreen().get()) {
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
windowInsetsController.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else if (readerPreferences.fullscreen().get()) {
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +588,7 @@ class ReaderActivity : BaseActivity() {
|
||||
try {
|
||||
readingModeToast?.cancel()
|
||||
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
|
||||
}
|
||||
}
|
||||
@@ -786,15 +781,24 @@ class ReaderActivity : BaseActivity() {
|
||||
* Updates viewer inset depending on fullscreen reader preferences.
|
||||
*/
|
||||
private fun updateViewerInset(fullscreen: Boolean) {
|
||||
viewModel.state.value.viewer?.getView()?.applyInsetter {
|
||||
if (!fullscreen) {
|
||||
type(navigationBars = true, statusBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
val view = viewModel.state.value.viewer?.getView() ?: return
|
||||
|
||||
view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen)
|
||||
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.
|
||||
*/
|
||||
@@ -902,15 +906,12 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
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) {
|
||||
true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
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(
|
||||
readerState: StateFlow<ReaderViewModel.State>,
|
||||
val hasDisplayCutout: Boolean,
|
||||
val onChangeReadingMode: (ReadingMode) -> Unit,
|
||||
val onChangeOrientation: (ReaderOrientation) -> Unit,
|
||||
val preferences: ReaderPreferences = Injekt.get(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
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.).
|
||||
*
|
||||
* Only works in Android 9+.
|
||||
* Only relevant from Android 9 to Android 14.
|
||||
*/
|
||||
fun Activity.hasDisplayCutout(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||
window.decorView.rootWindowInsets?.displayCutout != null
|
||||
fun View.hasDisplayCutout(): Boolean {
|
||||
return Build.VERSION.SDK_INT in Build.VERSION_CODES.P..Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||
rootWindowInsets?.displayCutout != null
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
android:layout_height="match_parent"
|
||||
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>
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.ReaderNavigationOverlayView
|
||||
@@ -30,7 +24,7 @@
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/dialog_root"
|
||||
android:id="@+id/compose_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user