Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/src/main/java/com/markreader/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ class MainActivity : ComponentActivity() {
.getInstance(applicationContext)
.preferences
.collectAsStateWithLifecycle(initialValue = UserPreferences())
MarkReaderTheme(theme = preferences.appThemeMode.toAppTheme()) {
MarkReaderTheme(
theme = preferences.appThemeMode.toAppTheme(),
dynamicColor = preferences.useDynamicColors
) {
MarkReaderApp(
externalUri = externalUri,
externalUriNonce = externalUriNonce,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.markreader.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
Expand All @@ -22,6 +23,7 @@ class PreferencesRepository private constructor(private val context: Context) {
private object Keys {
val LEGACY_THEME = stringPreferencesKey("theme")
val APP_THEME_MODE = stringPreferencesKey("app_theme_mode")
val USE_DYNAMIC_COLORS = booleanPreferencesKey("use_dynamic_colors")
val READER_LIGHT_THEME = stringPreferencesKey("reader_light_theme")
val READER_DARK_THEME = stringPreferencesKey("reader_dark_theme")
val READING_FONT = stringPreferencesKey("reading_font")
Expand Down Expand Up @@ -74,6 +76,7 @@ class PreferencesRepository private constructor(private val context: Context) {

UserPreferences(
appThemeMode = appThemeMode,
useDynamicColors = prefs[Keys.USE_DYNAMIC_COLORS] ?: true,
readerLightTheme = readerLightTheme,
readerDarkTheme = readerDarkTheme,
readingFont = readingFont,
Expand All @@ -88,6 +91,10 @@ class PreferencesRepository private constructor(private val context: Context) {
context.dataStore.edit { it[Keys.APP_THEME_MODE] = themeMode.name }
}

suspend fun setUseDynamicColors(enabled: Boolean) {
context.dataStore.edit { it[Keys.USE_DYNAMIC_COLORS] = enabled }
}

suspend fun setReaderLightTheme(theme: ReaderThemePreference) {
val safeTheme = if (theme == ReaderThemePreference.Sepia) ReaderThemePreference.Sepia else ReaderThemePreference.Light
context.dataStore.edit { it[Keys.READER_LIGHT_THEME] = safeTheme.name }
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/markreader/data/UserPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum class TextAlignmentPreference {

data class UserPreferences(
val appThemeMode: AppThemeModePreference = AppThemeModePreference.System,
val useDynamicColors: Boolean = true,
val readerLightTheme: ReaderThemePreference = ReaderThemePreference.Light,
val readerDarkTheme: ReaderThemePreference = ReaderThemePreference.Dark,
val readingFont: ReadingFontPreference = ReadingFontPreference.Merriweather,
Expand Down
90 changes: 85 additions & 5 deletions app/src/main/java/com/markreader/ui/screens/SettingsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.compose.material.icons.automirrored.rounded.MenuBook
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.rounded.BrightnessAuto
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Code
import androidx.compose.material.icons.rounded.DarkMode
import androidx.compose.material.icons.rounded.FormatAlignJustify
Expand All @@ -41,6 +42,7 @@ import androidx.compose.material.icons.rounded.FormatSize
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.TextFields
import androidx.compose.material.icons.rounded.Wallpaper
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
Expand All @@ -54,6 +56,8 @@ import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.dynamicDarkColorScheme
Expand Down Expand Up @@ -321,6 +325,63 @@ private fun <T> OptionSheet(
}
}

@Composable
private fun SwitchSettingsRow(
position: SegmentPosition,
icon: ImageVector,
title: String,
subtitle: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
val haptics = LocalHapticFeedback.current
val toggle: (Boolean) -> Unit = { newValue ->
haptics.performHapticFeedback(
if (newValue) HapticFeedbackType.ToggleOn else HapticFeedbackType.ToggleOff
)
onCheckedChange(newValue)
}

SettingsSurface(position = position, onClick = { toggle(!checked) }) {
Row(verticalAlignment = Alignment.CenterVertically) {
RowLeadingIcon(icon)
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.width(12.dp))
Switch(
checked = checked,
onCheckedChange = toggle,
thumbContent = {
AnimatedContent(
targetState = checked,
transitionSpec = {
fadeIn(tween(100)) togetherWith fadeOut(tween(100))
},
label = "switchThumbIcon"
) { isChecked ->
Icon(
imageVector = if (isChecked) Icons.Rounded.Check else Icons.Rounded.Close,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize)
)
}
}
)
}
}
}

@Composable
private fun SliderSettingsRow(
position: SegmentPosition,
Expand Down Expand Up @@ -419,11 +480,19 @@ private fun ReaderPreviewCard(
val isSystemDark = isSystemInDarkTheme()
val haptics = LocalHapticFeedback.current

val dynamicLightScheme = remember(context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) dynamicLightColorScheme(context) else null
val dynamicLightScheme = remember(context, preferences.useDynamicColors) {
if (preferences.useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicLightColorScheme(context)
} else {
null
}
}
val dynamicDarkScheme = remember(context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) dynamicDarkColorScheme(context) else null
val dynamicDarkScheme = remember(context, preferences.useDynamicColors) {
if (preferences.useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicDarkColorScheme(context)
} else {
null
}
}
val (lightReaderColors, darkReaderColors) = resolveReaderColors(
readerLightTheme = preferences.readerLightTheme,
Expand Down Expand Up @@ -590,9 +659,10 @@ fun SettingsScreen(
// ── Appearance ─────────────────────────────────────────────
Column {
SectionHeader("Appearance")
val supportsDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
SettingsGroup {
SegmentedSettingsRow(
position = SegmentPosition.Single,
position = if (supportsDynamicColors) SegmentPosition.First else SegmentPosition.Single,
icon = Icons.Rounded.Palette,
title = "App theme",
subtitle = "Overall look of the app"
Expand All @@ -602,6 +672,16 @@ fun SettingsScreen(
onSelect = viewModel::setAppThemeMode
)
}
if (supportsDynamicColors) {
SwitchSettingsRow(
position = SegmentPosition.Last,
icon = Icons.Rounded.Wallpaper,
title = "Use dynamic colors",
subtitle = "Tint the app from your wallpaper",
checked = preferences.useDynamicColors,
onCheckedChange = viewModel::setUseDynamicColors
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
viewModelScope.launch { repository.setAppThemeMode(theme) }
}

fun setUseDynamicColors(enabled: Boolean) {
viewModelScope.launch { repository.setUseDynamicColors(enabled) }
}

fun setReaderLightTheme(theme: ReaderThemePreference) {
viewModelScope.launch { repository.setReaderLightTheme(theme) }
}
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/java/com/markreader/ui/screens/ViewerScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,19 @@ fun ViewerScreen(
else -> "Rendered mode"
}

val dynamicLightScheme = remember(context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) dynamicLightColorScheme(context) else null
val dynamicLightScheme = remember(context, prefs.useDynamicColors) {
if (prefs.useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicLightColorScheme(context)
} else {
null
}
}
val dynamicDarkScheme = remember(context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) dynamicDarkColorScheme(context) else null
val dynamicDarkScheme = remember(context, prefs.useDynamicColors) {
if (prefs.useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicDarkColorScheme(context)
} else {
null
}
}

val (lightReaderColors, darkReaderColors) = resolveReaderColors(
Expand Down