mirror of https://github.com/Ashinch/ReadYou.git
379 lines
15 KiB
Kotlin
379 lines
15 KiB
Kotlin
package me.ash.reader.ui.page.settings.color
|
|
|
|
import android.content.Context
|
|
import android.os.Build
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.compose.animation.*
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.horizontalScroll
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.foundation.rememberScrollState
|
|
import androidx.compose.foundation.shape.CircleShape
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.outlined.Check
|
|
import androidx.compose.material.icons.outlined.Palette
|
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Surface
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.runtime.*
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.draw.clip
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.text.TextStyle
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.navigation.NavHostController
|
|
import me.ash.reader.R
|
|
import me.ash.reader.infrastructure.preference.*
|
|
import me.ash.reader.ui.component.base.*
|
|
import me.ash.reader.ui.ext.ExternalFonts
|
|
import me.ash.reader.ui.page.common.RouteName
|
|
import me.ash.reader.ui.page.settings.SettingItem
|
|
import me.ash.reader.ui.svg.PALETTE
|
|
import me.ash.reader.ui.svg.SVGString
|
|
import me.ash.reader.ui.theme.palette.*
|
|
import me.ash.reader.ui.theme.palette.TonalPalettes.Companion.toTonalPalettes
|
|
import me.ash.reader.ui.theme.palette.dynamic.extractTonalPalettesFromUserWallpaper
|
|
|
|
@Composable
|
|
fun ColorAndStylePage(
|
|
navController: NavHostController,
|
|
) {
|
|
val context = LocalContext.current
|
|
val darkTheme = LocalDarkTheme.current
|
|
val darkThemeNot = !darkTheme
|
|
val themeIndex = LocalThemeIndex.current
|
|
val customPrimaryColor = LocalCustomPrimaryColor.current
|
|
val fonts = LocalBasicFonts.current
|
|
val scope = rememberCoroutineScope()
|
|
|
|
val wallpaperTonalPalettes = extractTonalPalettesFromUserWallpaper()
|
|
var radioButtonSelected by remember { mutableStateOf(if (themeIndex > 4) 0 else 1) }
|
|
var fontsDialogVisible by remember { mutableStateOf(false) }
|
|
|
|
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
|
uri?.let {
|
|
ExternalFonts(context, it, ExternalFonts.FontType.BasicFont).copyToInternalStorage()
|
|
BasicFontsPreference.External.put(context, scope)
|
|
}
|
|
}
|
|
|
|
RYScaffold(
|
|
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
|
|
navigationIcon = {
|
|
FeedbackIconButton(
|
|
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
|
contentDescription = stringResource(R.string.back),
|
|
tint = MaterialTheme.colorScheme.onSurface
|
|
) {
|
|
navController.popBackStack()
|
|
}
|
|
},
|
|
content = {
|
|
LazyColumn {
|
|
item {
|
|
DisplayText(text = stringResource(R.string.color_and_style), desc = "")
|
|
}
|
|
item {
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(horizontal = 24.dp)
|
|
.aspectRatio(1.38f)
|
|
.clip(RoundedCornerShape(24.dp))
|
|
.background(
|
|
MaterialTheme.colorScheme.inverseOnSurface
|
|
onLight MaterialTheme.colorScheme.surface.copy(0.7f)
|
|
)
|
|
.clickable { },
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
DynamicSVGImage(
|
|
modifier = Modifier.padding(60.dp),
|
|
svgImageString = SVGString.PALETTE,
|
|
contentDescription = stringResource(R.string.color_and_style),
|
|
)
|
|
}
|
|
Spacer(modifier = Modifier.height(24.dp))
|
|
}
|
|
item {
|
|
BlockRadioButton(
|
|
selected = radioButtonSelected,
|
|
onSelected = { radioButtonSelected = it },
|
|
itemRadioGroups = listOf(
|
|
BlockRadioGroupButtonItem(
|
|
text = stringResource(R.string.wallpaper_colors),
|
|
onClick = {},
|
|
) {
|
|
Palettes(
|
|
context = context,
|
|
palettes = wallpaperTonalPalettes.run {
|
|
if (this.size > 5) {
|
|
this.subList(5, wallpaperTonalPalettes.size)
|
|
} else {
|
|
emptyList()
|
|
}
|
|
},
|
|
themeIndex = themeIndex,
|
|
themeIndexPrefix = 5,
|
|
customPrimaryColor = customPrimaryColor,
|
|
)
|
|
},
|
|
BlockRadioGroupButtonItem(
|
|
text = stringResource(R.string.basic_colors),
|
|
onClick = {},
|
|
) {
|
|
Palettes(
|
|
context = context,
|
|
themeIndex = themeIndex,
|
|
palettes = wallpaperTonalPalettes.subList(0, 5),
|
|
customPrimaryColor = customPrimaryColor,
|
|
)
|
|
},
|
|
),
|
|
)
|
|
Spacer(modifier = Modifier.height(24.dp))
|
|
}
|
|
item {
|
|
Subtitle(
|
|
modifier = Modifier.padding(horizontal = 24.dp),
|
|
text = stringResource(R.string.appearance),
|
|
)
|
|
SettingItem(
|
|
title = stringResource(R.string.dark_theme),
|
|
desc = darkTheme.toDesc(context),
|
|
separatedActions = true,
|
|
onClick = {
|
|
navController.navigate(RouteName.DARK_THEME) {
|
|
launchSingleTop = true
|
|
}
|
|
},
|
|
) {
|
|
RYSwitch(
|
|
activated = darkTheme.isDarkTheme()
|
|
) {
|
|
darkThemeNot.put(context, scope)
|
|
}
|
|
}
|
|
SettingItem(
|
|
title = stringResource(R.string.basic_fonts),
|
|
desc = fonts.toDesc(context),
|
|
onClick = { fontsDialogVisible = true },
|
|
) {}
|
|
Spacer(modifier = Modifier.height(24.dp))
|
|
}
|
|
item {
|
|
Subtitle(
|
|
modifier = Modifier.padding(horizontal = 24.dp),
|
|
text = stringResource(R.string.style)
|
|
)
|
|
SettingItem(
|
|
title = stringResource(R.string.feeds_page),
|
|
onClick = {
|
|
navController.navigate(RouteName.FEEDS_PAGE_STYLE) {
|
|
launchSingleTop = true
|
|
}
|
|
},
|
|
) {}
|
|
SettingItem(
|
|
title = stringResource(R.string.flow_page),
|
|
onClick = {
|
|
navController.navigate(RouteName.FLOW_PAGE_STYLE) {
|
|
launchSingleTop = true
|
|
}
|
|
},
|
|
) {}
|
|
SettingItem(
|
|
title = stringResource(R.string.reading_page),
|
|
onClick = {
|
|
navController.navigate(RouteName.READING_PAGE_STYLE) {
|
|
launchSingleTop = true
|
|
}
|
|
},
|
|
) {}
|
|
}
|
|
item {
|
|
Spacer(modifier = Modifier.height(24.dp))
|
|
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
RadioDialog(
|
|
visible = fontsDialogVisible,
|
|
title = stringResource(R.string.basic_fonts),
|
|
options = BasicFontsPreference.values.map {
|
|
RadioDialogOption(
|
|
text = it.toDesc(context),
|
|
style = TextStyle(fontFamily = it.asFontFamily(context)),
|
|
selected = it == fonts,
|
|
) {
|
|
if (it.value == BasicFontsPreference.External.value) {
|
|
launcher.launch("*/*")
|
|
} else {
|
|
it.put(context, scope)
|
|
}
|
|
}
|
|
}
|
|
) {
|
|
fontsDialogVisible = false
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Palettes(
|
|
context: Context,
|
|
palettes: List<TonalPalettes>,
|
|
themeIndex: Int = 0,
|
|
themeIndexPrefix: Int = 0,
|
|
customPrimaryColor: String = "",
|
|
) {
|
|
val scope = rememberCoroutineScope()
|
|
val tonalPalettes = customPrimaryColor.safeHexToColor().toTonalPalettes()
|
|
var addDialogVisible by remember { mutableStateOf(false) }
|
|
var customColorValue by remember { mutableStateOf(customPrimaryColor) }
|
|
|
|
if (palettes.isEmpty()) {
|
|
Row(
|
|
modifier = Modifier
|
|
.padding(horizontal = 24.dp)
|
|
.fillMaxWidth()
|
|
.height(80.dp)
|
|
.clip(RoundedCornerShape(16.dp))
|
|
.background(
|
|
MaterialTheme.colorScheme.inverseOnSurface
|
|
onLight MaterialTheme.colorScheme.surface.copy(0.7f),
|
|
)
|
|
.clickable {},
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
) {
|
|
Text(
|
|
text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1)
|
|
stringResource(R.string.no_palettes)
|
|
else stringResource(R.string.only_android_8_1_plus),
|
|
style = MaterialTheme.typography.labelLarge,
|
|
color = MaterialTheme.colorScheme.inverseSurface,
|
|
)
|
|
}
|
|
} else {
|
|
Row(
|
|
modifier = Modifier
|
|
.horizontalScroll(rememberScrollState())
|
|
.padding(horizontal = 24.dp),
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
) {
|
|
palettes.forEachIndexed { index, palette ->
|
|
val isCustom = index == palettes.lastIndex && themeIndexPrefix == 0
|
|
val i = themeIndex - themeIndexPrefix
|
|
SelectableMiniPalette(
|
|
selected = if (i >= palettes.size) i == 0 else i == index,
|
|
isCustom = isCustom,
|
|
onClick = {
|
|
if (isCustom) {
|
|
customColorValue = customPrimaryColor
|
|
addDialogVisible = true
|
|
} else {
|
|
ThemeIndexPreference.put(context, scope, themeIndexPrefix + index)
|
|
}
|
|
},
|
|
palette = if (isCustom) tonalPalettes else palette
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
TextFieldDialog(
|
|
visible = addDialogVisible,
|
|
title = stringResource(R.string.primary_color),
|
|
icon = Icons.Outlined.Palette,
|
|
value = customColorValue,
|
|
placeholder = stringResource(R.string.primary_color_hint),
|
|
onValueChange = {
|
|
customColorValue = it
|
|
},
|
|
onDismissRequest = {
|
|
addDialogVisible = false
|
|
},
|
|
onConfirm = {
|
|
it.checkColorHex()?.let {
|
|
CustomPrimaryColorPreference.put(context, scope, it)
|
|
ThemeIndexPreference.put(context, scope, 4)
|
|
addDialogVisible = false
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
fun SelectableMiniPalette(
|
|
modifier: Modifier = Modifier,
|
|
selected: Boolean,
|
|
isCustom: Boolean = false,
|
|
onClick: () -> Unit,
|
|
palette: TonalPalettes,
|
|
) {
|
|
Surface(
|
|
modifier = modifier,
|
|
shape = RoundedCornerShape(16.dp),
|
|
color = if (isCustom) {
|
|
MaterialTheme.colorScheme.primaryContainer
|
|
.copy(0.5f) onDark MaterialTheme.colorScheme.onPrimaryContainer.copy(0.3f)
|
|
} else {
|
|
MaterialTheme.colorScheme
|
|
.inverseOnSurface onLight MaterialTheme.colorScheme.surface.copy(0.7f)
|
|
},
|
|
) {
|
|
Surface(
|
|
modifier = Modifier
|
|
.clickable { onClick() }
|
|
.padding(16.dp)
|
|
.size(48.dp),
|
|
shape = CircleShape,
|
|
color = palette primary 90,
|
|
) {
|
|
Box {
|
|
Surface(
|
|
modifier = Modifier
|
|
.size(48.dp)
|
|
.offset((-24).dp, 24.dp),
|
|
color = palette tertiary 90,
|
|
) {}
|
|
Surface(
|
|
modifier = Modifier
|
|
.size(48.dp)
|
|
.offset(24.dp, 24.dp),
|
|
color = palette secondary 60,
|
|
) {}
|
|
AnimatedVisibility(
|
|
visible = selected,
|
|
modifier = Modifier
|
|
.align(Alignment.Center)
|
|
.clip(CircleShape)
|
|
.background(MaterialTheme.colorScheme.primary),
|
|
enter = fadeIn() + expandIn(expandFrom = Alignment.Center),
|
|
exit = shrinkOut(shrinkTowards = Alignment.Center) + fadeOut()
|
|
) {
|
|
Icon(
|
|
imageVector = Icons.Outlined.Check,
|
|
contentDescription = "Checked",
|
|
modifier = Modifier
|
|
.padding(8.dp)
|
|
.size(16.dp),
|
|
tint = MaterialTheme.colorScheme.surface
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|