🚀 Fullscreen option

* Added "Fullscreen" option in Reader
* If true, hides system bars and removes padding
* Placed in "Misc" subcategory
This commit is contained in:
Acclorite 2024-09-07 19:53:14 +03:00
parent 3de024457b
commit 5d7f3e09ac
14 changed files with 273 additions and 32 deletions

View file

@ -30,6 +30,7 @@ object DataStoreConstants {
val TEXT_ALIGNMENT = stringPreferencesKey("text_alignment")
val LETTER_SPACING = intPreferencesKey("letter_spacing")
val CUTOUT_PADDING = booleanPreferencesKey("cutout_padding")
val FULLSCREEN = booleanPreferencesKey("fullscreen")
// Browse settings
val BROWSE_FILES_STRUCTURE = stringPreferencesKey("browse_files_structure")

View file

@ -33,4 +33,5 @@ sealed class MainEvent {
data class OnChangeLetterSpacing(val spacing: Int) : MainEvent()
data class OnChangeAbsoluteDark(val bool: Boolean) : MainEvent()
data class OnChangeCutoutPadding(val bool: Boolean) : MainEvent()
data class OnChangeFullscreen(val bool: Boolean) : MainEvent()
}

View file

@ -70,6 +70,7 @@ data class MainState(
val textAlignment: ReaderTextAlignment = provideDefaultValue { ReaderTextAlignment.START },
val letterSpacing: Int = provideDefaultValue { 0 },
val cutoutPadding: Boolean = provideDefaultValue { false },
val fullscreen: Boolean = provideDefaultValue { true },
// Browse Settings
val browseFilesStructure: BrowseFilesStructure = provideDefaultValue {
@ -219,6 +220,10 @@ data class MainState(
cutoutPadding = provideValue(
CUTOUT_PADDING
) { cutoutPadding },
fullscreen = provideValue(
FULLSCREEN
) { fullscreen },
)
}
}

View file

@ -401,6 +401,20 @@ class MainViewModel @Inject constructor(
}
}
}
is MainEvent.OnChangeFullscreen -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(
DataStoreConstants.FULLSCREEN,
event.bool
)
updateStateWithSavedHandle {
it.copy(
fullscreen = event.bool
)
}
}
}
}
}

View file

@ -12,15 +12,19 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.LinearProgressIndicator
@ -42,6 +46,8 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
@ -78,6 +84,8 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
@Composable
fun ReaderScreenRoot(screen: Screen.Reader) {
val state = LocalReaderViewModel.current.state
val mainState = LocalMainViewModel.current.state
val onEvent = LocalReaderViewModel.current.onEvent
val onLibraryEvent = LocalLibraryViewModel.current.onEvent
val onHistoryEvent = LocalHistoryViewModel.current.onEvent
val viewModel = LocalReaderViewModel.current.viewModel
@ -101,7 +109,8 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
viewModel.init(
screen = screen,
onNavigate = onNavigate,
context = context,
fullscreenMode = mainState.value.fullscreen,
activity = context,
refreshList = {
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
onHistoryEvent(HistoryEvent.OnLoadList)
@ -114,9 +123,23 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
}
LaunchedEffect(canScroll) {
if (!canScroll && !state.value.showMenu && !state.value.loading) {
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
onEvent(
ReaderEvent.OnShowHideMenu(
fullscreenMode = mainState.value.fullscreen,
activity = context
)
)
}
}
LaunchedEffect(mainState.value.fullscreen) {
onEvent(
ReaderEvent.OnShowHideMenu(
show = state.value.showMenu,
fullscreenMode = mainState.value.fullscreen,
activity = context
)
)
}
LaunchedEffect(lazyListState) {
viewModel.onUpdateProgress {
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
@ -138,6 +161,7 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
}
}
@OptIn(ExperimentalLayoutApi::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
private fun ReaderScreen(lazyListState: LazyListState) {
@ -160,7 +184,12 @@ private fun ReaderScreen(lazyListState: LazyListState) {
val velocity = consumed.y
if ((velocity > 70 || velocity < -70) && state.value.showMenu && !state.value.lockMenu) {
onEvent(ReaderEvent.OnShowHideMenu(context = context))
onEvent(
ReaderEvent.OnShowHideMenu(
fullscreenMode = mainState.value.fullscreen,
activity = context
)
)
}
return super.onPostScroll(consumed, available, source)
@ -203,6 +232,64 @@ private fun ReaderScreen(lazyListState: LazyListState) {
}
}
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val cutoutInsets = WindowInsets.displayCutout
val systemBarsInsets = WindowInsets.systemBarsIgnoringVisibility
val cutoutInsetsPadding = remember(mainState.value.cutoutPadding) {
derivedStateOf {
cutoutInsets.asPaddingValues(density = density).run {
if (mainState.value.cutoutPadding) PaddingValues(
top = calculateTopPadding(),
start = calculateStartPadding(layoutDirection),
end = calculateEndPadding(layoutDirection),
bottom = calculateBottomPadding()
) else PaddingValues(0.dp)
}
}
}
val systemBarsInsetsPadding = remember(mainState.value.fullscreen) {
derivedStateOf {
systemBarsInsets.asPaddingValues(density = density).run {
if (!mainState.value.fullscreen) PaddingValues(
top = calculateTopPadding(),
start = calculateStartPadding(layoutDirection),
end = calculateEndPadding(layoutDirection),
bottom = calculateBottomPadding()
) else PaddingValues(0.dp)
}
}
}
val contentPadding = remember(
cutoutInsetsPadding.value,
systemBarsInsetsPadding.value
) {
PaddingValues(
top = systemBarsInsetsPadding.value.calculateTopPadding().run {
if (equals(0.dp)) return@run cutoutInsetsPadding.value
.calculateTopPadding()
this
},
start = systemBarsInsetsPadding.value.calculateStartPadding(layoutDirection).run {
if (equals(0.dp)) return@run cutoutInsetsPadding.value
.calculateStartPadding(layoutDirection)
this
},
end = systemBarsInsetsPadding.value.calculateEndPadding(layoutDirection).run {
if (equals(0.dp)) return@run cutoutInsetsPadding.value
.calculateEndPadding(layoutDirection)
this
},
bottom = systemBarsInsetsPadding.value.calculateBottomPadding().run {
if (equals(0.dp)) return@run cutoutInsetsPadding.value
.calculateBottomPadding()
this
}
)
}
if (state.value.showSettingsBottomSheet) {
ReaderSettingsBottomSheet()
}
@ -302,7 +389,10 @@ private fun ReaderScreen(lazyListState: LazyListState) {
indication = null,
onClick = {
onEvent(
ReaderEvent.OnShowHideMenu(context = context)
ReaderEvent.OnShowHideMenu(
fullscreenMode = mainState.value.fullscreen,
activity = context
)
)
}
)
@ -325,11 +415,7 @@ private fun ReaderScreen(lazyListState: LazyListState) {
.showToast(context = context, longToast = false)
}
)
.then(
if (mainState.value.cutoutPadding) Modifier.padding(
WindowInsets.displayCutout.asPaddingValues()
) else Modifier
),
.padding(contentPadding),
verticalArrangement = Arrangement.spacedBy(paragraphHeight),
contentPadding = PaddingValues(
top = (WindowInsets.displayCutout.asPaddingValues()
@ -355,6 +441,7 @@ private fun ReaderScreen(lazyListState: LazyListState) {
letterSpacing = letterSpacing,
sidePadding = sidePadding,
paragraphIndentation = mainState.value.paragraphIndentation,
fullscreenMode = mainState.value.fullscreen,
doubleClickTranslationEnabled = mainState.value.doubleClickTranslation,
toolbarHidden = toolbarHidden
)

View file

@ -42,6 +42,7 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.reader.data.
* @param letterSpacing Letter spacing.
* @param sidePadding Line's side padding.
* @param paragraphIndentation Whether Paragraph Indentation is enabled for this line.
* @param fullscreenMode Whether Reader is in fullscreen mode.
* @param doubleClickTranslationEnabled Whether Double Click Translation is enabled.
* @param toolbarHidden Whether selection toolBar is hidden.
*/
@ -59,6 +60,7 @@ fun LazyItemScope.ReaderTextParagraph(
letterSpacing: TextUnit,
sidePadding: Dp,
paragraphIndentation: Boolean,
fullscreenMode: Boolean,
doubleClickTranslationEnabled: Boolean,
toolbarHidden: Boolean
) {
@ -109,7 +111,10 @@ fun LazyItemScope.ReaderTextParagraph(
},
onClick = {
onEvent(
ReaderEvent.OnShowHideMenu(context = context as ComponentActivity)
ReaderEvent.OnShowHideMenu(
fullscreenMode = fullscreenMode,
activity = context as ComponentActivity
)
)
}
)

View file

@ -20,11 +20,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.presentation.core.components.LocalMainViewModel
import ua.acclorite.book_story.presentation.core.components.LocalReaderViewModel
import ua.acclorite.book_story.presentation.core.components.custom_bottom_sheet.CustomBottomSheet
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.FontSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.MiscSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.PaddingSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.TextSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.TranslatorSubcategory
@ -36,6 +38,7 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.reader.compo
@Composable
fun ReaderSettingsBottomSheet() {
val state = LocalReaderViewModel.current.state
val mainState = LocalMainViewModel.current.state
val onEvent = LocalReaderViewModel.current.onEvent
val context = LocalContext.current
@ -61,6 +64,7 @@ fun ReaderSettingsBottomSheet() {
onEvent(
ReaderEvent.OnShowHideMenu(
pagerState.currentPage != 2,
fullscreenMode = mainState.value.fullscreen,
context as ComponentActivity
)
)
@ -87,6 +91,11 @@ fun ReaderSettingsBottomSheet() {
topPadding = 22.dp,
bottomPadding = 0.dp
)
MiscSubcategory(
titleColor = { MaterialTheme.colorScheme.onSurface },
topPadding = 22.dp,
bottomPadding = 8.dp + it
)
}
}

View file

@ -17,7 +17,8 @@ sealed class ReaderEvent {
data class OnShowHideMenu(
val show: Boolean? = null,
val context: ComponentActivity
val fullscreenMode: Boolean,
val activity: ComponentActivity
) : ReaderEvent()
data class OnGoBack(

View file

@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.Book
@ -153,21 +154,11 @@ class ReaderViewModel @Inject constructor(
yield()
WindowCompat.getInsetsController(
event.context.window,
event.context.window.decorView
).apply {
yield()
systemBarsBehavior = WindowInsetsControllerCompat
.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (shouldShow) {
show(WindowInsetsCompat.Type.systemBars())
} else {
hide(WindowInsetsCompat.Type.systemBars())
}
}
showSystemBars(
show = shouldShow,
fullscreenMode = event.fullscreenMode,
activity = event.activity
)
_state.update {
it.copy(
showMenu = shouldShow
@ -405,7 +396,8 @@ class ReaderViewModel @Inject constructor(
fun init(
screen: Screen.Reader,
onNavigate: OnNavigate,
context: ComponentActivity,
fullscreenMode: Boolean,
activity: ComponentActivity,
refreshList: (Book) -> Unit,
onError: (UIText) -> Unit
) {
@ -425,10 +417,17 @@ class ReaderViewModel @Inject constructor(
clear()
launch {
launch(Dispatchers.Main) {
context.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}.join()
onEvent(ReaderEvent.OnShowHideMenu(false, context))
withContext(Dispatchers.Main) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
onEvent(
ReaderEvent.OnShowHideMenu(
show = false,
fullscreenMode = fullscreenMode,
activity = activity
)
)
onEvent(
ReaderEvent.OnLoadText(
refreshList = { refreshList(it) },
@ -495,6 +494,23 @@ class ReaderViewModel @Inject constructor(
}
}
private fun showSystemBars(
show: Boolean,
fullscreenMode: Boolean,
activity: ComponentActivity
): Boolean {
WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (show || !fullscreenMode) show(WindowInsetsCompat.Type.systemBars())
else hide(WindowInsetsCompat.Type.systemBars())
}
return show || !fullscreenMode
}
private suspend fun clear() {
eventJob.cancel()
eventJob.join()

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.presentation.core.components.CategoryTitle
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.FontSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.MiscSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.PaddingSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.TextSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.TranslatorSubcategory
@ -43,6 +44,11 @@ fun LazyListScope.ReaderSettingsCategory(
bottomPadding = 0.dp
)
TranslatorSubcategory(
titleColor = titleColor,
topPadding = 22.dp,
bottomPadding = 0.dp
)
MiscSubcategory(
titleColor = titleColor,
topPadding = 22.dp,
bottomPadding = bottomPadding

View file

@ -0,0 +1,31 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.LocalMainViewModel
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
/**
* Fullscreen setting.
* If true, hides system bars in Reader.
*/
@Composable
fun FullscreenSetting() {
val state = LocalMainViewModel.current.state
val onMainEvent = LocalMainViewModel.current.onEvent
SwitchWithTitle(
selected = state.value.fullscreen,
title = stringResource(id = R.string.fullscreen_option),
description = stringResource(id = R.string.fullscreen_option_desc),
onClick = {
onMainEvent(
MainEvent.OnChangeFullscreen(
!state.value.fullscreen
)
)
}
)
}

View file

@ -0,0 +1,59 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.CategoryTitle
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FullscreenSetting
/**
* Misc subcategory.
* Contains all settings that cannot be categorized.
*/
fun LazyListScope.MiscSubcategory(
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.misc_reader_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
item {
FullscreenSetting()
}
item {
Spacer(
modifier = Modifier.height(bottomPadding)
)
}
}

View file

@ -200,6 +200,7 @@
<string name="text_reader_settings">Текст</string>
<string name="translator_reader_settings">Перекладач</string>
<string name="padding_reader_settings">Відступи</string>
<string name="misc_reader_settings">Інше</string>
<string name="theme_appearance_settings">Налаштування теми</string>
<string name="colors_appearance_settings">Кольори</string>
<string name="general_browse_settings">Головні</string>
@ -238,7 +239,9 @@
<string name="text_alignment_option">Розташування текста</string>
<string name="letter_spacing_option">Відстань між літерами</string>
<string name="cutout_padding_option">Відступ від виїмки</string>
<string name="cutout_padding_option_desc">Застосуйте відступ до ділянки виїмки</string>
<string name="cutout_padding_option_desc">Застосувати відступ до ділянки виїмки</string>
<string name="fullscreen_option">Повний екран</string>
<string name="fullscreen_option_desc">Сховати системні панелі та прибрати відступи</string>
<!-- Browse -->
<string name="browse_files_structure_option">Структура файлів</string>

View file

@ -217,6 +217,7 @@
<string name="text_reader_settings">Text</string>
<string name="translator_reader_settings">Translator</string>
<string name="padding_reader_settings">Padding</string>
<string name="misc_reader_settings">Misc</string>
<string name="theme_appearance_settings">Theme preferences</string>
<string name="colors_appearance_settings">Colors</string>
<string name="general_browse_settings">General</string>
@ -256,6 +257,8 @@
<string name="letter_spacing_option">Letter spacing</string>
<string name="cutout_padding_option">Cutout padding</string>
<string name="cutout_padding_option_desc">Apply padding to cutout area</string>
<string name="fullscreen_option">Fullscreen</string>
<string name="fullscreen_option_desc">Hide system bars and remove padding</string>
<!-- Browse -->
<string name="browse_files_structure_option">Files structure</string>