Add dark theme settings

This commit is contained in:
Ash 2022-05-05 15:48:35 +08:00
parent ae1e4cb4ee
commit 7933aa4d11
11 changed files with 262 additions and 28 deletions

View File

@ -0,0 +1,41 @@
package me.ash.reader.data.preference
import android.content.Context
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class AmoledDarkThemePreference(val value: Boolean) : Preference() {
object ON : AmoledDarkThemePreference(true)
object OFF : AmoledDarkThemePreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(
DataStoreKeys.AmoledDarkTheme,
value
)
}
}
companion object {
val default = OFF
val values = listOf(ON, OFF)
fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.AmoledDarkTheme.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
operator fun AmoledDarkThemePreference.not(): AmoledDarkThemePreference =
when (value) {
true -> AmoledDarkThemePreference.OFF
false -> AmoledDarkThemePreference.ON
}

View File

@ -0,0 +1,66 @@
package me.ash.reader.data.preference
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class DarkThemePreference(val value: Int) : Preference() {
object UseDeviceTheme : DarkThemePreference(0)
object ON : DarkThemePreference(1)
object OFF : DarkThemePreference(2)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(
DataStoreKeys.DarkTheme,
value
)
}
}
fun getDesc(context: Context): String =
when (this) {
UseDeviceTheme -> context.getString(R.string.use_device_theme)
ON -> context.getString(R.string.on)
OFF -> context.getString(R.string.off)
}
@Composable
fun isDarkTheme(): Boolean = when (this) {
UseDeviceTheme -> isSystemInDarkTheme()
ON -> true
OFF -> false
}
companion object {
val default = UseDeviceTheme
val values = listOf(UseDeviceTheme, ON, OFF)
fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.DarkTheme.key]) {
0 -> UseDeviceTheme
1 -> ON
2 -> OFF
else -> default
}
}
}
@Composable
operator fun DarkThemePreference.not(): DarkThemePreference =
when (this) {
DarkThemePreference.UseDeviceTheme -> if (isSystemInDarkTheme()) {
DarkThemePreference.OFF
} else {
DarkThemePreference.ON
}
DarkThemePreference.ON -> DarkThemePreference.OFF
DarkThemePreference.OFF -> DarkThemePreference.ON
}

View File

@ -14,6 +14,8 @@ import me.ash.reader.ui.ext.dataStore
data class Settings(
val themeIndex: Int = ThemeIndexPreference.default,
val customPrimaryColor: String = CustomPrimaryColorPreference.default,
val darkTheme: DarkThemePreference = DarkThemePreference.default,
val amoledDarkTheme: AmoledDarkThemePreference = AmoledDarkThemePreference.default,
val feedsFilterBarStyle: FeedsFilterBarStylePreference = FeedsFilterBarStylePreference.default,
val feedsFilterBarFilled: FeedsFilterBarFilledPreference = FeedsFilterBarFilledPreference.default,
@ -41,6 +43,8 @@ fun Preferences.toSettings(): Settings {
return Settings(
themeIndex = ThemeIndexPreference.fromPreferences(this),
customPrimaryColor = CustomPrimaryColorPreference.fromPreferences(this),
darkTheme = DarkThemePreference.fromPreferences(this),
amoledDarkTheme = AmoledDarkThemePreference.fromPreferences(this),
feedsFilterBarStyle = FeedsFilterBarStylePreference.fromPreferences(this),
feedsFilterBarFilled = FeedsFilterBarFilledPreference.fromPreferences(this),
@ -60,7 +64,9 @@ fun Preferences.toSettings(): Settings {
flowArticleListImage = FlowArticleListImagePreference.fromPreferences(this),
flowArticleListDesc = FlowArticleListDescPreference.fromPreferences(this),
flowArticleListTime = FlowArticleListTimePreference.fromPreferences(this),
flowArticleListDateStickyHeader = FlowArticleListDateStickyHeaderPreference.fromPreferences(this),
flowArticleListDateStickyHeader = FlowArticleListDateStickyHeaderPreference.fromPreferences(
this
),
flowArticleListTonalElevation = FlowArticleListTonalElevationPreference.fromPreferences(this),
)
}
@ -80,6 +86,8 @@ fun SettingsProvider(
CompositionLocalProvider(
LocalThemeIndex provides settings.themeIndex,
LocalCustomPrimaryColor provides settings.customPrimaryColor,
LocalDarkTheme provides settings.darkTheme,
LocalAmoledDarkTheme provides settings.amoledDarkTheme,
LocalFeedsTopBarTonalElevation provides settings.feedsTopBarTonalElevation,
LocalFeedsGroupListExpand provides settings.feedsGroupListExpand,
@ -110,6 +118,10 @@ val LocalThemeIndex =
compositionLocalOf { ThemeIndexPreference.default }
val LocalCustomPrimaryColor =
compositionLocalOf { CustomPrimaryColorPreference.default }
val LocalDarkTheme =
compositionLocalOf<DarkThemePreference> { DarkThemePreference.default }
val LocalAmoledDarkTheme =
compositionLocalOf<AmoledDarkThemePreference> { AmoledDarkThemePreference.default }
val LocalFeedsFilterBarStyle =
compositionLocalOf<FeedsFilterBarStylePreference> { FeedsFilterBarStylePreference.default }

View File

@ -13,8 +13,8 @@ import coil.compose.AsyncImage
import coil.imageLoader
import coil.request.ImageRequest
import com.caverock.androidsvg.SVG
import me.ash.reader.data.preference.LocalDarkTheme
import me.ash.reader.ui.svg.parseDynamicColor
import me.ash.reader.ui.theme.LocalUseDarkTheme
import me.ash.reader.ui.theme.palette.LocalTonalPalettes
@Composable
@ -24,7 +24,7 @@ fun DynamicSVGImage(
contentDescription: String,
) {
val context = LocalContext.current
val useDarkTheme = LocalUseDarkTheme.current
val useDarkTheme = LocalDarkTheme.current.isDarkTheme()
val tonalPalettes = LocalTonalPalettes.current
var size by remember { mutableStateOf(IntSize.Zero) }
val pic by remember(tonalPalettes, size) {

View File

@ -130,6 +130,16 @@ sealed class DataStoreKeys<T> {
get() = stringPreferencesKey("customPrimaryColor")
}
object DarkTheme : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("darkTheme")
}
object AmoledDarkTheme : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("amoledDarkTheme")
}
object FeedsFilterBarStyle : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("feedsFilterBarStyle")

View File

@ -14,6 +14,7 @@ import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import me.ash.reader.data.entity.Filter
import me.ash.reader.data.preference.LocalDarkTheme
import me.ash.reader.ui.ext.*
import me.ash.reader.ui.page.home.HomeViewAction
import me.ash.reader.ui.page.home.HomeViewModel
@ -22,13 +23,13 @@ import me.ash.reader.ui.page.home.flow.FlowPage
import me.ash.reader.ui.page.home.read.ReadPage
import me.ash.reader.ui.page.settings.SettingsPage
import me.ash.reader.ui.page.settings.color.ColorAndStyle
import me.ash.reader.ui.page.settings.color.DarkTheme
import me.ash.reader.ui.page.settings.color.feeds.FeedsPageStyle
import me.ash.reader.ui.page.settings.color.flow.FlowPageStyle
import me.ash.reader.ui.page.settings.interaction.Interaction
import me.ash.reader.ui.page.settings.tips.TipsAndSupport
import me.ash.reader.ui.page.startup.StartupPage
import me.ash.reader.ui.theme.AppTheme
import me.ash.reader.ui.theme.LocalUseDarkTheme
@OptIn(ExperimentalAnimationApi::class, androidx.compose.material.ExperimentalMaterialApi::class)
@Composable
@ -86,8 +87,9 @@ fun HomeEntry(
}
}
AppTheme {
val useDarkTheme = LocalUseDarkTheme.current
val useDarkTheme = LocalDarkTheme.current.isDarkTheme()
AppTheme(useDarkTheme = useDarkTheme) {
rememberSystemUiController().run {
setStatusBarColor(Color.Transparent, !useDarkTheme)
@ -129,6 +131,9 @@ fun HomeEntry(
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
ColorAndStyle(navController)
}
animatedComposable(route = RouteName.DARK_THEME) {
DarkTheme(navController)
}
animatedComposable(route = RouteName.FEEDS_PAGE_STYLE) {
FeedsPageStyle(navController)
}

View File

@ -14,6 +14,7 @@ object RouteName {
// Color & Style
const val COLOR_AND_STYLE = "color_and_style"
const val DARK_THEME = "dark_theme"
const val FEEDS_PAGE_STYLE = "feeds_page_style"
const val FLOW_PAGE_STYLE = "flow_page_style"

View File

@ -26,16 +26,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.data.preference.CustomPrimaryColorPreference
import me.ash.reader.data.preference.LocalCustomPrimaryColor
import me.ash.reader.data.preference.LocalThemeIndex
import me.ash.reader.data.preference.ThemeIndexPreference
import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.*
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.LocalUseDarkTheme
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
@ -47,9 +43,11 @@ fun ColorAndStyle(
navController: NavHostController,
) {
val context = LocalContext.current
val useDarkTheme = LocalUseDarkTheme.current
val darkTheme = LocalDarkTheme.current
val darkThemeNot = !darkTheme
val themeIndex = LocalThemeIndex.current
val customPrimaryColor = LocalCustomPrimaryColor.current
val scope = rememberCoroutineScope()
val wallpaperTonalPalettes = extractTonalPalettesFromUserWallpaper()
var radioButtonSelected by remember { mutableStateOf(if (themeIndex > 4) 0 else 1) }
@ -151,12 +149,19 @@ fun ColorAndStyle(
)
SettingItem(
title = stringResource(R.string.dark_theme),
desc = stringResource(R.string.use_device_theme),
enable = false,
desc = darkTheme.getDesc(context),
separatedActions = true,
onClick = {},
onClick = {
navController.navigate(RouteName.DARK_THEME) {
launchSingleTop = true
}
},
) {
Switch(activated = useDarkTheme, enable = false)
Switch(
activated = darkTheme.isDarkTheme()
) {
darkThemeNot.put(context, scope)
}
}
SettingItem(
title = stringResource(R.string.basic_fonts),

View File

@ -0,0 +1,100 @@
package me.ash.reader.ui.page.settings.color
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
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.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.data.preference.DarkThemePreference
import me.ash.reader.data.preference.LocalAmoledDarkTheme
import me.ash.reader.data.preference.LocalDarkTheme
import me.ash.reader.data.preference.not
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.component.Subtitle
import me.ash.reader.ui.component.Switch
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onLight
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DarkTheme(
navController: NavHostController,
) {
val context = LocalContext.current
val darkTheme = LocalDarkTheme.current
val amoledDarkTheme = LocalAmoledDarkTheme.current
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface)
.statusBarsPadding()
.navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
topBar = {
SmallTopAppBar(
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface
),
title = {},
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {
navController.popBackStack()
}
},
actions = {}
)
},
content = {
LazyColumn {
item {
DisplayText(text = stringResource(R.string.dark_theme), desc = "")
}
item {
DarkThemePreference.values.map {
SettingItem(
title = it.getDesc(context),
onClick = {
it.put(context, scope)
},
) {
RadioButton(selected = it == darkTheme, onClick = {
it.put(context, scope)
})
}
}
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = "其他",
)
SettingItem(
title = "Amoled 的深色主题",
onClick = {
(!amoledDarkTheme).put(context, scope)
},
) {
Switch(activated = amoledDarkTheme.value) {
(!amoledDarkTheme).put(context, scope)
}
}
}
}
}
)
}

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import me.ash.reader.data.preference.LocalThemeIndex
import me.ash.reader.ui.theme.palette.LocalTonalPalettes
import me.ash.reader.ui.theme.palette.TonalPalettes
@ -13,8 +12,6 @@ import me.ash.reader.ui.theme.palette.dynamic.extractTonalPalettesFromUserWallpa
import me.ash.reader.ui.theme.palette.dynamicDarkColorScheme
import me.ash.reader.ui.theme.palette.dynamicLightColorScheme
val LocalUseDarkTheme = compositionLocalOf { false }
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
@ -38,7 +35,6 @@ fun AppTheme(
ProvideZcamViewingConditions {
CompositionLocalProvider(
LocalTonalPalettes provides tonalPalettes.also { it.Preheating() },
LocalUseDarkTheme provides useDarkTheme,
) {
MaterialTheme(
colorScheme =

View File

@ -6,7 +6,7 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import me.ash.reader.ui.theme.LocalUseDarkTheme
import me.ash.reader.data.preference.LocalDarkTheme
@Composable
fun dynamicLightColorScheme(): ColorScheme {
@ -68,20 +68,18 @@ fun dynamicDarkColorScheme(): ColorScheme {
)
}
@Suppress("NOTHING_TO_INLINE")
@Composable
inline infix fun Color.onLight(lightColor: Color): Color =
if (!LocalUseDarkTheme.current) lightColor else this
infix fun Color.onLight(lightColor: Color): Color =
if (!LocalDarkTheme.current.isDarkTheme()) lightColor else this
@Suppress("NOTHING_TO_INLINE")
@Composable
inline infix fun Color.onDark(darkColor: Color): Color =
if (LocalUseDarkTheme.current) darkColor else this
infix fun Color.onDark(darkColor: Color): Color =
if (LocalDarkTheme.current.isDarkTheme()) darkColor else this
@Composable
infix fun Color.alwaysLight(isAlways: Boolean): Color {
val colorScheme = MaterialTheme.colorScheme
return if (isAlways && LocalUseDarkTheme.current) {
return if (isAlways && LocalDarkTheme.current.isDarkTheme()) {
when (this) {
colorScheme.primary -> colorScheme.onPrimary
colorScheme.secondary -> colorScheme.onSecondary