Changed SettingsScreen structure. (more scalable way)

This commit is contained in:
acclorite 2024-07-19 01:48:24 +03:00
parent 3aefb37935
commit 0665ff62e9
31 changed files with 1338 additions and 940 deletions

View file

@ -4,10 +4,8 @@ import androidx.activity.ComponentActivity
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Spacer
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.height
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
@ -23,21 +21,13 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.CustomBottomSheet import ua.acclorite.book_story.presentation.components.CustomBottomSheet
import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.ReaderSettingsCategory
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
/** /**
* Settings bottom sheet. Has General and Colors categories. * Settings bottom sheet. Has General and Colors categories.
@ -57,12 +47,6 @@ fun ReaderSettingsBottomSheet(
val currentPage by remember { derivedStateOf { pagerState.currentPage } } val currentPage by remember { derivedStateOf { pagerState.currentPage } }
val context = LocalContext.current val context = LocalContext.current
val fontFamily = remember(mainState.value.fontFamily) {
Constants.FONTS.find {
it.id == mainState.value.fontFamily
} ?: Constants.FONTS[0]
}
val scrimColor = if (currentPage == 1) Color.Transparent val scrimColor = if (currentPage == 1) Color.Transparent
else BottomSheetDefaults.ScrimColor else BottomSheetDefaults.ScrimColor
@ -109,213 +93,24 @@ fun ReaderSettingsBottomSheet(
HorizontalPager(state = pagerState) { page -> HorizontalPager(state = pagerState) { page ->
LazyColumn { LazyColumn {
if (page == 0) { if (page == 0) {
item { ReaderSettingsCategory(
Spacer(modifier = Modifier.height(22.dp)) state = mainState,
} onMainEvent = onMainEvent,
item { titleColor = { MaterialTheme.colorScheme.onSurface },
CategoryTitle( topPadding = 16.dp,
title = stringResource(id = R.string.font_reader_settings), bottomPadding = 8.dp + it
color = MaterialTheme.colorScheme.onSurface )
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
ChipsWithTitle(
title = stringResource(id = R.string.font_family_option),
chips = Constants.FONTS
.map {
ButtonItem(
it.id,
it.fontName.asString(),
MaterialTheme.typography.labelLarge.copy(
fontFamily = it.font
),
it.id == fontFamily.id
)
},
onClick = {
onMainEvent(MainEvent.OnChangeFontFamily(it.id))
}
)
}
item {
ChipsWithTitle(
title = stringResource(id = R.string.font_style_option),
chips = listOf(
ButtonItem(
"normal",
stringResource(id = R.string.font_style_normal),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Normal
),
!mainState.value.isItalic!!
),
ButtonItem(
"italic",
stringResource(id = R.string.font_style_italic),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Italic
),
mainState.value.isItalic!!
),
),
onClick = {
onMainEvent(
MainEvent.OnChangeFontStyle(
when (it.id) {
"italic" -> true
else -> false
}
)
)
}
)
}
item {
SliderWithTitle(
value = mainState.value.fontSize!! to "pt",
fromValue = 10,
toValue = 35,
title = stringResource(id = R.string.font_size_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontSize(it)
)
}
)
}
item {
Spacer(modifier = Modifier.height(22.dp))
}
item {
CategoryTitle(
title = stringResource(id = R.string.text_reader_settings),
color = MaterialTheme.colorScheme.onSurface
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SliderWithTitle(
value = mainState.value.sidePadding!! to "pt",
fromValue = 1,
toValue = 20,
title = stringResource(id = R.string.side_padding_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeSidePadding(it)
)
}
)
}
item {
SliderWithTitle(
value = mainState.value.lineHeight!! to "pt",
fromValue = 1,
toValue = 16,
title = stringResource(id = R.string.line_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeLineHeight(it)
)
}
)
}
item {
SliderWithTitle(
value = mainState.value.paragraphHeight!! to "pt",
fromValue = 0,
toValue = 24,
title = stringResource(id = R.string.paragraph_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeParagraphHeight(it)
)
}
)
}
item {
SwitchWithTitle(
selected = mainState.value.paragraphIndentation!!,
title = stringResource(id = R.string.paragraph_indentation_option)
) {
onMainEvent(
MainEvent.OnChangeParagraphIndentation(!mainState.value.paragraphIndentation!!)
)
}
}
item {
Spacer(modifier = Modifier.height(22.dp))
}
item {
CategoryTitle(
title = stringResource(id = R.string.translator_reader_settings),
color = MaterialTheme.colorScheme.onSurface
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SwitchWithTitle(
selected = mainState.value.doubleClickTranslation!!,
title = stringResource(id = R.string.translator_double_click_to_translate_option),
description = stringResource(id = R.string.translator_double_click_to_translate_option_desc),
onClick = {
onMainEvent(
MainEvent.OnChangeDoubleClickTranslation(
!mainState.value.doubleClickTranslation!!
)
)
}
)
}
} }
}
LazyColumn {
if (page == 1) { if (page == 1) {
item { ColorsSubcategory(
Spacer(modifier = Modifier.height(14.dp)) state = mainState,
} onMainEvent = onMainEvent,
item { showTitle = false,
ColorPickerWithTitle( topPadding = 16.dp,
value = Color(mainState.value.backgroundColor!!.toULong()), bottomPadding = 8.dp + it
title = stringResource(id = R.string.background_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeBackgroundColor(
it.value.toLong()
)
)
}
)
}
item {
ColorPickerWithTitle(
value = Color(mainState.value.fontColor!!.toULong()),
title = stringResource(id = R.string.font_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontColor(
it.value.toLong()
)
)
}
)
}
}
item {
Spacer(
modifier = Modifier.height(
8.dp + it
)
) )
} }
} }

View file

@ -23,6 +23,7 @@ import ua.acclorite.book_story.presentation.components.CategoryTitle
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun ChipsWithTitle( fun ChipsWithTitle(
modifier: Modifier = Modifier,
title: String, title: String,
chips: List<ButtonItem>, chips: List<ButtonItem>,
horizontalPadding: Dp = 18.dp, horizontalPadding: Dp = 18.dp,
@ -30,7 +31,7 @@ fun ChipsWithTitle(
onClick: (ButtonItem) -> Unit onClick: (ButtonItem) -> Unit
) { ) {
Column( Column(
Modifier modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = horizontalPadding, vertical = verticalPadding) .padding(horizontal = horizontalPadding, vertical = verticalPadding)
) { ) {

View file

@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
*/ */
@Composable @Composable
fun SwitchWithTitle( fun SwitchWithTitle(
modifier: Modifier = Modifier,
selected: Boolean, selected: Boolean,
title: String, title: String,
description: String? = null, description: String? = null,
@ -38,7 +39,7 @@ fun SwitchWithTitle(
onClick: () -> Unit onClick: () -> Unit
) { ) {
Row( Row(
modifier = Modifier modifier = modifier
.clickable { onClick() } .clickable { onClick() }
.padding(horizontal = horizontalPadding, vertical = verticalPadding), .padding(horizontal = horizontalPadding, vertical = verticalPadding),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically

View file

@ -1,9 +1,7 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance package ua.acclorite.book_story.presentation.screens.settings.nested.appearance
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
@ -15,39 +13,21 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.AppearanceSettingsCategory
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
import ua.acclorite.book_story.presentation.ui.DarkTheme
import ua.acclorite.book_story.presentation.ui.PureDark
import ua.acclorite.book_story.presentation.ui.SlidingTransition
import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.ThemeContrast
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
@Composable @Composable
fun AppearanceSettingsRoot() { fun AppearanceSettingsRoot() {
@ -71,13 +51,6 @@ private fun AppearanceSettings(
onMainEvent: (MainEvent) -> Unit onMainEvent: (MainEvent) -> Unit
) { ) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState() val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
val themeContrastTheme = remember { mutableStateOf(state.value.theme!!) }
LaunchedEffect(state.value.theme) {
if (themeContrastTheme.value != state.value.theme && state.value.theme != Theme.DYNAMIC) {
themeContrastTheme.value = state.value.theme!!
}
}
Scaffold( Scaffold(
Modifier Modifier
@ -107,189 +80,10 @@ private fun AppearanceSettings(
.padding(top = paddingValues.calculateTopPadding()), .padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second state = scrollState.second
) { ) {
item { AppearanceSettingsCategory(
Spacer( state = state,
modifier = Modifier onMainEvent = onMainEvent
.animateItem() )
.height(16.dp)
)
}
item {
CategoryTitle(
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.theme_appearance_settings),
color = MaterialTheme.colorScheme.primary
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(8.dp)
)
}
item {
SegmentedButtonWithTitle(
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.dark_theme_option),
locked = true,
buttons = DarkTheme.entries.map {
ButtonItem(
it.toString(),
when (it) {
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
},
MaterialTheme.typography.labelLarge,
it == state.value.darkTheme
)
}
) {
onMainEvent(
MainEvent.OnChangeDarkTheme(
it.id
)
)
}
}
item {
AppearanceSettingsThemeSwitcher(
modifier = Modifier.animateItem(),
state = state,
onMainEvent = onMainEvent
)
}
item {
BookStoryTheme(
theme = themeContrastTheme.value,
isDark = state.value.darkTheme!!.isDark(),
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
themeContrast = state.value.themeContrast!!
) {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = state.value.theme != Theme.DYNAMIC,
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.theme_contrast_option),
locked = state.value.theme != Theme.DYNAMIC,
buttons = ThemeContrast.entries.map {
ButtonItem(
it.toString(),
when (it) {
ThemeContrast.STANDARD -> stringResource(id = R.string.theme_contrast_standard)
ThemeContrast.MEDIUM -> stringResource(id = R.string.theme_contrast_medium)
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
},
MaterialTheme.typography.labelLarge,
it == state.value.themeContrast
)
}
) {
onMainEvent(
MainEvent.OnChangeThemeContrast(
it.id
)
)
}
}
}
}
item {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = state.value.darkTheme!!.isDark(),
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.pure_dark_option),
locked = true,
buttons = PureDark.entries.map {
ButtonItem(
it.toString(),
when (it) {
PureDark.OFF -> stringResource(id = R.string.pure_dark_off)
PureDark.ON -> stringResource(id = R.string.pure_dark_on)
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
},
MaterialTheme.typography.labelLarge,
it == state.value.pureDark
)
}
) {
onMainEvent(
MainEvent.OnChangePureDark(
it.id
)
)
}
}
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(22.dp)
)
CategoryTitle(
title = stringResource(id = R.string.colors_appearance_settings),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.animateItem()
)
Spacer(
modifier = Modifier
.animateItem()
.height(8.dp)
)
}
item {
ColorPickerWithTitle(
modifier = Modifier.animateItem(),
value = Color(state.value.backgroundColor!!.toULong()),
title = stringResource(id = R.string.background_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeBackgroundColor(
it.value.toLong()
)
)
}
)
}
item {
ColorPickerWithTitle(
modifier = Modifier.animateItem(),
value = Color(state.value.fontColor!!.toULong()),
title = stringResource(id = R.string.font_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontColor(
it.value.toLong()
)
)
}
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(48.dp)
)
}
} }
} }
} }

View file

@ -0,0 +1,48 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ThemePreferencesSubcategory
/**
* Appearance Settings Category.
* Contains all Appearance settings such as Theme.
*
* @param state [MainState] instance.
* @param onMainEvent [MainEvent] callback.
* @param titleColor Title's color.
* @param topPadding Top padding to be applied.
* @param bottomPadding Bottom padding to be applied.
*/
fun LazyListScope.AppearanceSettingsCategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
topPadding: Dp = 16.dp,
bottomPadding: Dp = 48.dp
) {
ThemePreferencesSubcategory(
state = state,
onMainEvent = onMainEvent,
titleColor = titleColor,
topPadding = topPadding,
bottomPadding = 0.dp
)
ColorsSubcategory(
state,
onMainEvent = onMainEvent,
titleColor = titleColor,
topPadding = 22.dp,
bottomPadding = bottomPadding
)
}

View file

@ -0,0 +1,48 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
import ua.acclorite.book_story.presentation.ui.DarkTheme
/**
* Dark Theme setting.
* If true, dark theme applied to the app's theme.
*/
@Composable
fun LazyItemScope.DarkThemeSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.dark_theme_option),
modifier = Modifier.animateItem(),
locked = true,
buttons = DarkTheme.entries.map {
ButtonItem(
it.toString(),
when (it) {
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
},
MaterialTheme.typography.labelLarge,
it == state.value.darkTheme
)
}
) {
onMainEvent(
MainEvent.OnChangeDarkTheme(
it.id
)
)
}
}

View file

@ -0,0 +1,57 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
import ua.acclorite.book_story.presentation.ui.PureDark
import ua.acclorite.book_story.presentation.ui.SlidingTransition
import ua.acclorite.book_story.presentation.ui.isDark
/**
* Pure Dark setting.
* If true, enables Pure Dark (OLED) theme.
*/
@Composable
fun LazyItemScope.PureDarkSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = state.value.darkTheme!!.isDark(),
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.pure_dark_option),
locked = true,
buttons = PureDark.entries.map {
ButtonItem(
it.toString(),
when (it) {
PureDark.OFF -> stringResource(id = R.string.pure_dark_off)
PureDark.ON -> stringResource(id = R.string.pure_dark_on)
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
},
MaterialTheme.typography.labelLarge,
it == state.value.pureDark
)
}
) {
onMainEvent(
MainEvent.OnChangePureDark(
it.id
)
)
}
}
}

View file

@ -0,0 +1,79 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
import ua.acclorite.book_story.presentation.ui.SlidingTransition
import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.ThemeContrast
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
/**
* Theme Contrast setting.
* Lets user change theme contrast levels.
*/
@Composable
fun LazyItemScope.ThemeContrastSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
val themeContrastTheme = remember { mutableStateOf(state.value.theme!!) }
LaunchedEffect(state.value.theme) {
if (themeContrastTheme.value != state.value.theme && state.value.theme != Theme.DYNAMIC) {
themeContrastTheme.value = state.value.theme!!
}
}
BookStoryTheme(
theme = themeContrastTheme.value,
isDark = state.value.darkTheme!!.isDark(),
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
themeContrast = state.value.themeContrast!!
) {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = state.value.theme != Theme.DYNAMIC,
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.theme_contrast_option),
locked = state.value.theme != Theme.DYNAMIC,
buttons = ThemeContrast.entries.map {
ButtonItem(
it.toString(),
when (it) {
ThemeContrast.STANDARD -> stringResource(id = R.string.theme_contrast_standard)
ThemeContrast.MEDIUM -> stringResource(id = R.string.theme_contrast_medium)
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
},
MaterialTheme.typography.labelLarge,
it == state.value.themeContrast
)
}
) {
onMainEvent(
MainEvent.OnChangeThemeContrast(
it.id
)
)
}
}
}
}

View file

@ -1,5 +1,6 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
import android.os.Build
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@ -10,6 +11,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -17,6 +19,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -25,21 +29,80 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme 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.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.domain.util.UIText import ua.acclorite.book_story.domain.util.UIText
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.components.customItems
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.ui.Theme import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.ThemeContrast import ua.acclorite.book_story.presentation.ui.ThemeContrast
import ua.acclorite.book_story.presentation.ui.animatedColorScheme import ua.acclorite.book_story.presentation.ui.animatedColorScheme
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
/** /**
* Theme switcher item. Used to change app theme. * Theme setting.
* Lets user change app's theme from available in [Constants.THEMES].
*/
@Composable
fun LazyItemScope.ThemeSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
verticalPadding: Dp = 8.dp,
horizontalPadding: Dp = 18.dp
) {
val themes = remember {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Constants.THEMES
else Constants.THEMES.dropWhile { it.first == Theme.DYNAMIC }
}
Column(
Modifier
.animateItem()
.fillMaxWidth()
.padding(vertical = verticalPadding)
) {
CategoryTitle(
title = stringResource(id = R.string.app_theme_option),
padding = horizontalPadding
)
Spacer(modifier = Modifier.height(8.dp))
LazyRow(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = horizontalPadding)
) {
customItems(themes, key = { it.first.name }) { themeEntry ->
ThemeSettingItem(
theme = themeEntry,
darkTheme = state.value.darkTheme!!.isDark(),
themeContrast = state.value.themeContrast!!,
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
selected = state.value.theme == themeEntry.first
) {
onMainEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
}
}
}
}
}
/**
* Theme Setting item.
* Used to switch between different themes.
* *
* @param theme Target [Theme]. * @param theme Target [Theme].
* @param darkTheme Whether should be shown in Dark Theme. * @param darkTheme Whether should be shown in Dark Theme.
@ -49,7 +112,7 @@ import ua.acclorite.book_story.presentation.ui.animatedColorScheme
* @param onClick OnClick callback. * @param onClick OnClick callback.
*/ */
@Composable @Composable
fun AppearanceSettingsThemeSwitcherItem( private fun ThemeSettingItem(
theme: Pair<Theme, UIText>, theme: Pair<Theme, UIText>,
darkTheme: Boolean, darkTheme: Boolean,
isPureDark: Boolean, isPureDark: Boolean,

View file

@ -0,0 +1,96 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.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.runtime.State
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.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
/**
* Colors subcategory.
* Contains all settings from Colors.
*/
fun LazyListScope.ColorsSubcategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.colors_appearance_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.animateItem()
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
// todo: This will be replaced with ColorPresets
item {
ColorPickerWithTitle(
modifier = Modifier.animateItem(),
value = Color(state.value.backgroundColor!!.toULong()),
title = stringResource(id = R.string.background_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeBackgroundColor(
it.value.toLong()
)
)
}
)
}
item {
ColorPickerWithTitle(
modifier = Modifier.animateItem(),
value = Color(state.value.fontColor!!.toULong()),
title = stringResource(id = R.string.font_color_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontColor(
it.value.toLong()
)
)
}
)
}
// ---------
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -0,0 +1,96 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.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.runtime.State
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.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.DarkThemeSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.PureDarkSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.ThemeContrastSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.ThemeSetting
/**
* Theme Preferences subcategory.
* Contains all settings from Theme Preferences.
*/
fun LazyListScope.ThemePreferencesSubcategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.theme_appearance_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.animateItem()
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
item {
DarkThemeSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
ThemeSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
ThemeContrastSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
PureDarkSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -1,80 +0,0 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher
import android.os.Build
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.customItemsIndexed
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
/**
* Theme switcher.
*/
@Composable
fun AppearanceSettingsThemeSwitcher(
modifier: Modifier = Modifier,
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
verticalPadding: Dp = 8.dp
) {
val themes = remember {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Constants.THEMES
else Constants.THEMES.dropWhile { it.first == Theme.DYNAMIC }
}
Column(
modifier
.fillMaxWidth()
.padding(vertical = verticalPadding)
) {
CategoryTitle(
title = stringResource(id = R.string.app_theme_option)
)
Spacer(modifier = Modifier.height(10.dp))
LazyRow(
Modifier
.fillMaxWidth(),
) {
customItemsIndexed(themes, key = { it.first.name }) { index, themeEntry ->
if (index == 0) {
Spacer(modifier = Modifier.width(18.dp))
} else {
Spacer(modifier = Modifier.width(8.dp))
}
AppearanceSettingsThemeSwitcherItem(
theme = themeEntry,
darkTheme = state.value.darkTheme!!.isDark(),
themeContrast = state.value.themeContrast!!,
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
selected = state.value.theme == themeEntry.first
) {
onMainEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
}
if (index == themes.lastIndex) {
Spacer(modifier = Modifier.width(18.dp))
}
}
}
}
}

View file

@ -1,12 +1,7 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.general package ua.acclorite.book_story.presentation.screens.settings.nested.general
import android.Manifest
import android.annotation.SuppressLint
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
@ -22,15 +17,9 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
@ -38,10 +27,9 @@ import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.GeneralSettingsCategory
@Composable @Composable
fun GeneralSettingsRoot() { fun GeneralSettingsRoot() {
@ -59,8 +47,8 @@ fun GeneralSettingsRoot() {
) )
} }
@SuppressLint("InlinedApi")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun GeneralSettings( private fun GeneralSettings(
state: State<MainState>, state: State<MainState>,
@ -69,11 +57,6 @@ private fun GeneralSettings(
onMainEvent: (MainEvent) -> Unit onMainEvent: (MainEvent) -> Unit
) { ) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState() val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
val activity = LocalContext.current as ComponentActivity
val notificationsPermissionState = rememberPermissionState(
permission = Manifest.permission.POST_NOTIFICATIONS
)
Scaffold( Scaffold(
Modifier Modifier
@ -103,53 +86,11 @@ private fun GeneralSettings(
.padding(top = paddingValues.calculateTopPadding()), .padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second state = scrollState.second
) { ) {
item { GeneralSettingsCategory(
Spacer(modifier = Modifier.height(8.dp)) state = state,
} onMainEvent = onMainEvent,
item { onSettingsEvent = onSettingsEvent
ChipsWithTitle( )
title = stringResource(id = R.string.language_option),
chips = Constants.LANGUAGES.sortedBy { it.second }.map {
ButtonItem(
it.first,
it.second,
MaterialTheme.typography.labelLarge,
it.first == state.value.language
)
}.sortedBy { it.title }
) {
onMainEvent(
MainEvent.OnChangeLanguage(
it.id
)
)
}
}
item {
SwitchWithTitle(
selected = state.value.checkForUpdates!!,
title = stringResource(id = R.string.check_for_updates_option),
description = stringResource(id = R.string.check_for_updates_option_desc)
) {
onSettingsEvent(
SettingsEvent.OnGeneralChangeCheckForUpdates(
enable = !state.value.checkForUpdates!!,
activity = activity,
notificationsPermissionState = notificationsPermissionState,
onChangeCheckForUpdates = {
onMainEvent(
MainEvent.OnChangeCheckForUpdates(
it
)
)
}
)
)
}
}
item { Spacer(modifier = Modifier.height(48.dp)) }
} }
} }
} }

View file

@ -0,0 +1,66 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.general.components
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.AppLanguageSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.CheckForUpdatesSetting
/**
* General Settings Category.
* Contains all General settings such as App Language.
*
* @param state [MainState] instance.
* @param onMainEvent [MainEvent] callback.
* @param onSettingsEvent [SettingsEvent] callback.
* @param topPadding Top padding to be applied.
* @param bottomPadding Bottom padding to be applied.
*/
fun LazyListScope.GeneralSettingsCategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
onSettingsEvent: (SettingsEvent) -> Unit,
topPadding: Dp = 16.dp,
bottomPadding: Dp = 48.dp
) {
item {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
item {
AppLanguageSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
CheckForUpdatesSetting(
state = state,
onMainEvent = onMainEvent,
onSettingsEvent = onSettingsEvent
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -0,0 +1,43 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
/**
* App Language setting.
* Changes app's language.
*/
@Composable
fun LazyItemScope.AppLanguageSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
ChipsWithTitle(
title = stringResource(id = R.string.language_option),
modifier = Modifier.animateItem(),
chips = Constants.LANGUAGES.sortedBy { it.second }.map {
ButtonItem(
it.first,
it.second,
MaterialTheme.typography.labelLarge,
it.first == state.value.language
)
}.sortedBy { it.title }
) {
onMainEvent(
MainEvent.OnChangeLanguage(
it.id
)
)
}
}

View file

@ -0,0 +1,58 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings
import android.Manifest
import android.annotation.SuppressLint
import androidx.activity.ComponentActivity
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
/**
* Check For Updates setting.
* If true, checks for the update on app start.
*/
@OptIn(ExperimentalPermissionsApi::class)
@SuppressLint("InlinedApi")
@Composable
fun LazyItemScope.CheckForUpdatesSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
onSettingsEvent: (SettingsEvent) -> Unit
) {
val activity = LocalContext.current as ComponentActivity
val notificationsPermissionState = rememberPermissionState(
permission = Manifest.permission.POST_NOTIFICATIONS
)
SwitchWithTitle(
selected = state.value.checkForUpdates!!,
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.check_for_updates_option),
description = stringResource(id = R.string.check_for_updates_option_desc)
) {
onSettingsEvent(
SettingsEvent.OnGeneralChangeCheckForUpdates(
enable = !state.value.checkForUpdates!!,
activity = activity,
notificationsPermissionState = notificationsPermissionState,
onChangeCheckForUpdates = {
onMainEvent(
MainEvent.OnChangeCheckForUpdates(
it
)
)
}
)
)
}
}

View file

@ -1,9 +1,7 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader package ua.acclorite.book_story.presentation.screens.settings.nested.reader
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
@ -17,27 +15,19 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.ReaderSettingsCategory
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
@Composable @Composable
fun ReaderSettingsRoot() { fun ReaderSettingsRoot() {
@ -62,12 +52,6 @@ private fun ReaderSettings(
) { ) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState() val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
val fontFamily = remember(state.value.fontFamily) {
Constants.FONTS.find {
it.id == state.value.fontFamily
} ?: Constants.FONTS[0]
}
Scaffold( Scaffold(
Modifier Modifier
.fillMaxSize() .fillMaxSize()
@ -96,177 +80,10 @@ private fun ReaderSettings(
.padding(top = paddingValues.calculateTopPadding()), .padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second state = scrollState.second
) { ) {
item { ReaderSettingsCategory(
Spacer(modifier = Modifier.height(16.dp)) state = state,
} onMainEvent = onMainEvent
item { )
CategoryTitle(
title = stringResource(id = R.string.font_reader_settings),
color = MaterialTheme.colorScheme.primary
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
ChipsWithTitle(
title = stringResource(id = R.string.font_family_option),
chips = Constants.FONTS
.map {
ButtonItem(
it.id,
it.fontName.asString(),
MaterialTheme.typography.labelLarge.copy(
fontFamily = it.font
),
it.id == fontFamily.id
)
},
onClick = {
onMainEvent(MainEvent.OnChangeFontFamily(it.id))
}
)
}
item {
ChipsWithTitle(
title = stringResource(id = R.string.font_style_option),
chips = listOf(
ButtonItem(
"normal",
stringResource(id = R.string.font_style_normal),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Normal
),
!state.value.isItalic!!
),
ButtonItem(
"italic",
stringResource(id = R.string.font_style_italic),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Italic
),
state.value.isItalic!!
),
),
onClick = {
onMainEvent(
MainEvent.OnChangeFontStyle(
when (it.id) {
"italic" -> true
else -> false
}
)
)
}
)
}
item {
SliderWithTitle(
value = state.value.fontSize!! to "pt",
fromValue = 10,
toValue = 35,
title = stringResource(id = R.string.font_size_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontSize(it)
)
}
)
}
item {
Spacer(modifier = Modifier.height(22.dp))
}
item {
CategoryTitle(
title = stringResource(id = R.string.text_reader_settings),
color = MaterialTheme.colorScheme.primary
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SliderWithTitle(
value = state.value.sidePadding!! to "pt",
fromValue = 1,
toValue = 20,
title = stringResource(id = R.string.side_padding_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeSidePadding(it)
)
}
)
}
item {
SliderWithTitle(
value = state.value.lineHeight!! to "pt",
fromValue = 1,
toValue = 16,
title = stringResource(id = R.string.line_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeLineHeight(it)
)
}
)
}
item {
SliderWithTitle(
value = state.value.paragraphHeight!! to "pt",
fromValue = 0,
toValue = 24,
title = stringResource(id = R.string.paragraph_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeParagraphHeight(it)
)
}
)
}
item {
SwitchWithTitle(
selected = state.value.paragraphIndentation!!,
title = stringResource(id = R.string.paragraph_indentation_option)
) {
onMainEvent(
MainEvent.OnChangeParagraphIndentation(!state.value.paragraphIndentation!!)
)
}
}
item {
Spacer(modifier = Modifier.height(22.dp))
}
item {
CategoryTitle(
title = stringResource(id = R.string.translator_reader_settings),
color = MaterialTheme.colorScheme.primary
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SwitchWithTitle(
selected = state.value.doubleClickTranslation!!,
title = stringResource(id = R.string.translator_double_click_to_translate_option),
description = stringResource(id = R.string.translator_double_click_to_translate_option_desc),
onClick = {
onMainEvent(
MainEvent.OnChangeDoubleClickTranslation(
!state.value.doubleClickTranslation!!
)
)
}
)
}
item { Spacer(modifier = Modifier.height(48.dp)) }
} }
} }
} }

View file

@ -0,0 +1,57 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
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.TextSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.subcategories.TranslatorSubcategory
/**
* Reader Settings Category.
* Contains all Reader settings.
*
* @param state [MainState] instance.
* @param onMainEvent [MainEvent] callback.
* @param titleColor Color that [CategoryTitle] has.
* @param topPadding Top padding to be applied.
* @param bottomPadding Bottom padding to be applied.
*/
fun LazyListScope.ReaderSettingsCategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
topPadding: Dp = 16.dp,
bottomPadding: Dp = 48.dp
) {
FontSubcategory(
state = state,
onMainEvent = onMainEvent,
titleColor = titleColor,
topPadding = topPadding,
bottomPadding = 0.dp
)
TextSubcategory(
state,
onMainEvent = onMainEvent,
titleColor = titleColor,
topPadding = 22.dp,
bottomPadding = 0.dp
)
TranslatorSubcategory(
state,
onMainEvent = onMainEvent,
titleColor = titleColor,
topPadding = 22.dp,
bottomPadding = bottomPadding
)
}

View file

@ -0,0 +1,35 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
/**
* Double Click Translation setting.
* Changes Reader's double click translation.
*/
@Composable
fun LazyItemScope.DoubleClickTranslationSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SwitchWithTitle(
selected = state.value.doubleClickTranslation!!,
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.translator_double_click_to_translate_option),
description = stringResource(id = R.string.translator_double_click_to_translate_option_desc),
onClick = {
onMainEvent(
MainEvent.OnChangeDoubleClickTranslation(
!state.value.doubleClickTranslation!!
)
)
}
)
}

View file

@ -0,0 +1,50 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
/**
* Font Family setting.
* Changes Reader's font, uses fonts from [Constants.FONTS].
*/
@Composable
fun LazyItemScope.FontFamilySetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
val fontFamily = remember(state.value.fontFamily) {
Constants.FONTS.find {
it.id == state.value.fontFamily
} ?: Constants.FONTS[0]
}
ChipsWithTitle(
title = stringResource(id = R.string.font_family_option),
modifier = Modifier.animateItem(),
chips = Constants.FONTS
.map {
ButtonItem(
it.id,
it.fontName.asString(),
MaterialTheme.typography.labelLarge.copy(
fontFamily = it.font
),
it.id == fontFamily.id
)
},
onClick = {
onMainEvent(MainEvent.OnChangeFontFamily(it.id))
}
)
}

View file

@ -0,0 +1,34 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
/**
* Font Size setting.
* Changes Reader's font size.
*/
@Composable
fun LazyItemScope.FontSizeSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SliderWithTitle(
value = state.value.fontSize!! to "pt",
modifier = Modifier.animateItem(),
fromValue = 10,
toValue = 35,
title = stringResource(id = R.string.font_size_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeFontSize(it)
)
}
)
}

View file

@ -0,0 +1,67 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
/**
* Font Style setting.
* Changes Reader's font style (Normal/Italic).
*/
@Composable
fun LazyItemScope.FontStyleSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
val fontFamily = remember(state.value.fontFamily) {
Constants.FONTS.find {
it.id == state.value.fontFamily
} ?: Constants.FONTS[0]
}
ChipsWithTitle(
title = stringResource(id = R.string.font_style_option),
modifier = Modifier.animateItem(),
chips = listOf(
ButtonItem(
"normal",
stringResource(id = R.string.font_style_normal),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Normal
),
!state.value.isItalic!!
),
ButtonItem(
"italic",
stringResource(id = R.string.font_style_italic),
MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Italic
),
state.value.isItalic!!
),
),
onClick = {
onMainEvent(
MainEvent.OnChangeFontStyle(
when (it.id) {
"italic" -> true
else -> false
}
)
)
}
)
}

View file

@ -0,0 +1,34 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
/**
* Line Height setting.
* Changes Reader's line height.
*/
@Composable
fun LazyItemScope.LineHeightSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SliderWithTitle(
value = state.value.lineHeight!! to "pt",
modifier = Modifier.animateItem(),
fromValue = 1,
toValue = 16,
title = stringResource(id = R.string.line_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeLineHeight(it)
)
}
)
}

View file

@ -0,0 +1,34 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
/**
* Paragraph Height setting.
* Changes Reader's paragraph height.
*/
@Composable
fun LazyItemScope.ParagraphHeightSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SliderWithTitle(
value = state.value.paragraphHeight!! to "pt",
modifier = Modifier.animateItem(),
fromValue = 0,
toValue = 24,
title = stringResource(id = R.string.paragraph_height_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeParagraphHeight(it)
)
}
)
}

View file

@ -0,0 +1,31 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
/**
* Paragraph Indentation setting.
* Changes Reader's paragraph indentation.
*/
@Composable
fun LazyItemScope.ParagraphIndentationSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SwitchWithTitle(
selected = state.value.paragraphIndentation!!,
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.paragraph_indentation_option)
) {
onMainEvent(
MainEvent.OnChangeParagraphIndentation(!state.value.paragraphIndentation!!)
)
}
}

View file

@ -0,0 +1,34 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
/**
* Side Padding setting.
* Changes Reader's side padding.
*/
@Composable
fun LazyItemScope.SidePaddingSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SliderWithTitle(
value = state.value.sidePadding!! to "pt",
modifier = Modifier.animateItem(),
fromValue = 1,
toValue = 20,
title = stringResource(id = R.string.side_padding_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeSidePadding(it)
)
}
)
}

View file

@ -0,0 +1,88 @@
@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.runtime.State
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.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FontFamilySetting
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FontSizeSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FontStyleSetting
/**
* Font subcategory.
* Contains all settings from Font.
*/
fun LazyListScope.FontSubcategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.font_reader_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.animateItem()
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
item {
FontFamilySetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
FontStyleSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
FontSizeSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -0,0 +1,96 @@
@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.runtime.State
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.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.LineHeightSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.ParagraphHeightSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.ParagraphIndentationSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.SidePaddingSetting
/**
* Text subcategory.
* Contains all settings from Text.
*/
fun LazyListScope.TextSubcategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.text_reader_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.animateItem()
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
item {
SidePaddingSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
LineHeightSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
ParagraphHeightSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
ParagraphIndentationSetting(
state = state,
onMainEvent = onMainEvent
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -0,0 +1,72 @@
@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.runtime.State
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.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.DoubleClickTranslationSetting
/**
* Translator subcategory.
* Contains all settings from Translator.
*/
fun LazyListScope.TranslatorSubcategory(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.translator_reader_settings) },
showTitle: Boolean = true,
topPadding: Dp,
bottomPadding: Dp
) {
item {
if (showTitle) {
CategoryTitle(
title = title.invoke(),
color = titleColor.invoke(),
modifier = Modifier
.animateItem()
.padding(
top = topPadding,
bottom = 8.dp
)
)
} else {
Spacer(
modifier = Modifier
.animateItem()
.height((topPadding - 8.dp).coerceAtLeast(0.dp))
)
}
}
item {
DoubleClickTranslationSetting(
state = state,
onMainEvent
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(bottomPadding)
)
}
}

View file

@ -1,172 +1,26 @@
package ua.acclorite.book_story.presentation.screens.start.components package ua.acclorite.book_story.presentation.screens.start.components
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ThemePreferencesSubcategory
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
import ua.acclorite.book_story.presentation.ui.DarkTheme
import ua.acclorite.book_story.presentation.ui.PureDark
import ua.acclorite.book_story.presentation.ui.SlidingTransition
import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.ThemeContrast
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
/** /**
* Appearance settings. * Appearance settings.
*/ */
fun LazyListScope.startAppearanceScreen( fun LazyListScope.startAppearanceScreen(
mainState: State<MainState>, mainState: State<MainState>,
onMainEvent: (MainEvent) -> Unit, onMainEvent: (MainEvent) -> Unit
themeContrastTheme: State<Theme>
) { ) {
item { ThemePreferencesSubcategory(
Spacer( state = mainState,
modifier = Modifier onMainEvent = onMainEvent,
.animateItem() title = { stringResource(id = R.string.start_theme_preferences) },
.height(16.dp) topPadding = 16.dp,
) bottomPadding = 8.dp
} )
item {
CategoryTitle(
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.start_theme_preferences),
color = MaterialTheme.colorScheme.primary
)
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(8.dp)
)
}
item {
SegmentedButtonWithTitle(
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.dark_theme_option),
locked = true,
buttons = DarkTheme.entries.map {
ButtonItem(
it.toString(),
when (it) {
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
},
MaterialTheme.typography.labelLarge,
it == mainState.value.darkTheme
)
}
) {
onMainEvent(
MainEvent.OnChangeDarkTheme(
it.id
)
)
}
}
item {
AppearanceSettingsThemeSwitcher(
modifier = Modifier.animateItem(),
state = mainState,
onMainEvent = onMainEvent
)
}
item {
BookStoryTheme(
theme = themeContrastTheme.value,
isDark = mainState.value.darkTheme!!.isDark(),
isPureDark = mainState.value.pureDark!!.isPureDark(context = LocalContext.current),
themeContrast = mainState.value.themeContrast!!
) {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = mainState.value.theme != Theme.DYNAMIC,
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.theme_contrast_option),
locked = mainState.value.theme != Theme.DYNAMIC,
buttons = ThemeContrast.entries.map {
ButtonItem(
it.toString(),
when (it) {
ThemeContrast.STANDARD -> stringResource(id = R.string.theme_contrast_standard)
ThemeContrast.MEDIUM -> stringResource(id = R.string.theme_contrast_medium)
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
},
MaterialTheme.typography.labelLarge,
it == mainState.value.themeContrast
)
}
) {
onMainEvent(
MainEvent.OnChangeThemeContrast(
it.id
)
)
}
}
}
}
item {
SlidingTransition(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
),
visible = mainState.value.darkTheme!!.isDark(),
) {
SegmentedButtonWithTitle(
title = stringResource(id = R.string.pure_dark_option),
locked = true,
buttons = PureDark.entries.map {
ButtonItem(
it.toString(),
when (it) {
PureDark.OFF -> stringResource(id = R.string.pure_dark_off)
PureDark.ON -> stringResource(id = R.string.pure_dark_on)
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
},
MaterialTheme.typography.labelLarge,
it == mainState.value.pureDark
)
}
) {
onMainEvent(
MainEvent.OnChangePureDark(
it.id
)
)
}
}
}
item {
Spacer(
modifier = Modifier
.animateItem()
.height(8.dp)
)
}
} }

View file

@ -19,9 +19,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme 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.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -40,7 +38,6 @@ import ua.acclorite.book_story.presentation.screens.start.components.permissions
import ua.acclorite.book_story.presentation.screens.start.data.StartEvent import ua.acclorite.book_story.presentation.screens.start.data.StartEvent
import ua.acclorite.book_story.presentation.screens.start.data.StartNavigationScreen import ua.acclorite.book_story.presentation.screens.start.data.StartNavigationScreen
import ua.acclorite.book_story.presentation.screens.start.data.StartState import ua.acclorite.book_story.presentation.screens.start.data.StartState
import ua.acclorite.book_story.presentation.ui.Theme
/** /**
* Start Settings. * Start Settings.
@ -74,13 +71,6 @@ fun StartSettings(
) )
}.sortedBy { it.title } }.sortedBy { it.title }
} }
val themeContrastTheme = remember { mutableStateOf(mainState.value.theme!!) }
LaunchedEffect(mainState.value.theme) {
if (themeContrastTheme.value != mainState.value.theme && mainState.value.theme != Theme.DYNAMIC) {
themeContrastTheme.value = mainState.value.theme!!
}
}
StartNavigationTransition( StartNavigationTransition(
modifier = Modifier.padding(horizontal = 18.dp), modifier = Modifier.padding(horizontal = 18.dp),
@ -148,8 +138,7 @@ fun StartSettings(
) { ) {
startAppearanceScreen( startAppearanceScreen(
mainState = mainState, mainState = mainState,
onMainEvent = onMainEvent, onMainEvent = onMainEvent
themeContrastTheme = themeContrastTheme
) )
} }