Skip to content

Commit

Permalink
add custom theme color support
Browse files Browse the repository at this point in the history
  • Loading branch information
Tlaster committed May 7, 2024
1 parent 7e32739 commit ddac7ee
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 17 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ dependencies {
debugImplementation(libs.mlkit.language.id.debug)
implementation(projects.shared)
implementation(libs.androidx.splash)
implementation(libs.materialKolor)
implementation(libs.colorpicker.compose)

if (project.file("google-services.json").exists()) {
implementation(platform(libs.firebase.bom))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.HideSource
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.datastore.core.DataStore
import androidx.datastore.core.Serializer
Expand All @@ -32,6 +33,7 @@ val LocalAppearanceSettings = staticCompositionLocalOf { AppearanceSettings() }
data class AppearanceSettings(
val theme: Theme = Theme.SYSTEM,
val dynamicTheme: Boolean = true,
val colorSeed: ULong = Color.Blue.value,
val avatarShape: AvatarShape = AvatarShape.CIRCLE,
val showActions: Boolean = true,
val showNumbers: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private fun RowScope.StatusFooterComponent(
leadingIcon = {
Icon(
imageVector = Icons.Default.Report,
contentDescription = null,
contentDescription = stringResource(id = R.string.blusky_item_action_report),
)
},
onClick = {
Expand All @@ -250,7 +250,7 @@ private fun RowScope.StatusFooterComponent(
leadingIcon = {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
contentDescription = stringResource(id = R.string.blusky_item_action_delete),
)
},
onClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
Expand Down Expand Up @@ -43,6 +47,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.ColorPickerDialogRouteDestination
import dev.dimension.flare.R
import dev.dimension.flare.data.model.AppearanceSettings
import dev.dimension.flare.data.model.AvatarShape
Expand Down Expand Up @@ -75,6 +80,9 @@ internal fun AppearanceRoute(navigator: ProxyDestinationsNavigator) {
SharedTransitionScope {
AppearanceScreen(
onBack = navigator::navigateUp,
toColorPicker = {
navigator.navigate(ColorPickerDialogRouteDestination)
},
)
}
}
Expand All @@ -83,7 +91,10 @@ internal fun AppearanceRoute(navigator: ProxyDestinationsNavigator) {
context(AnimatedVisibilityScope, SharedTransitionScope)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
@Composable
private fun AppearanceScreen(onBack: () -> Unit) {
private fun AppearanceScreen(
onBack: () -> Unit,
toColorPicker: () -> Unit,
) {
val state by producePresenter { appearancePresenter() }
val appearanceSettings = LocalAppearanceSettings.current
FlareScaffold(
Expand Down Expand Up @@ -272,6 +283,31 @@ private fun AppearanceScreen(onBack: () -> Unit) {
},
)
}
AnimatedVisibility(visible = !appearanceSettings.dynamicTheme || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
ListItem(
headlineContent = {
Text(text = stringResource(id = R.string.settings_appearance_theme_color))
},
supportingContent = {
Text(text = stringResource(id = R.string.settings_appearance_theme_color_description))
},
trailingContent = {
Box(
modifier =
Modifier
.background(
color = MaterialTheme.colorScheme.primary,
shape = CircleShape,
)
.size(36.dp),
)
},
modifier =
Modifier.clickable {
toColorPicker.invoke()
},
)
}
BoxWithConstraints {
var showMenu by remember { mutableStateOf(false) }
ListItem(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package dev.dimension.flare.ui.screen.settings

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.skydoves.colorpicker.compose.AlphaTile
import com.github.skydoves.colorpicker.compose.BrightnessSlider
import com.github.skydoves.colorpicker.compose.ColorEnvelope
import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.spec.DestinationStyle
import dev.dimension.flare.R
import dev.dimension.flare.data.model.LocalAppearanceSettings
import dev.dimension.flare.data.repository.SettingsRepository
import dev.dimension.flare.molecule.producePresenter
import dev.dimension.flare.ui.component.ThemeWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.koin.compose.koinInject

@Destination<RootGraph>(
wrappers = [ThemeWrapper::class],
style = DestinationStyle.Dialog::class,
)
@Composable
internal fun ColorPickerDialogRoute(navigator: ProxyDestinationsNavigator) {
ColorPickerDialog(
onBack = navigator::navigateUp,
)
}

@Composable
private fun ColorPickerDialog(onBack: () -> Unit) {
val appearanceSettings = LocalAppearanceSettings.current
val state by producePresenter {
presenter(initialColor = appearanceSettings.colorSeed)
}
val controller = rememberColorPickerController()
LaunchedEffect(Unit) {
controller.selectByColor(Color(appearanceSettings.colorSeed), fromUser = true)
}

AlertDialog(
onDismissRequest = onBack,
confirmButton = {
TextButton(
onClick = {
state.confirm()
onBack.invoke()
},
) {
Text(stringResource(id = android.R.string.ok))
}
},
text = {
Column(
verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(16.dp),
) {
HsvColorPicker(
modifier =
Modifier
.fillMaxWidth()
.aspectRatio(1f),
controller = controller,
onColorChanged = { colorEnvelope: ColorEnvelope ->
state.setColor(colorEnvelope.color)
},
)
BrightnessSlider(
modifier =
Modifier
.fillMaxWidth()
.height(36.dp),
controller = controller,
)
AlphaTile(
modifier =
Modifier
.fillMaxWidth()
.height(40.dp)
.clip(RoundedCornerShape(6.dp)),
controller = controller,
)
}
},
title = {
Text(stringResource(id = R.string.settings_appearance_theme_color))
},
)
}

@Composable
private fun presenter(
initialColor: ULong,
settingsRepository: SettingsRepository = koinInject(),
coroutineScope: CoroutineScope = koinInject(),
) = run {
var selectedColor by remember { mutableStateOf(Color(initialColor)) }

object {
fun setColor(color: Color) {
selectedColor = color
}

fun confirm() {
coroutineScope.launch {
settingsRepository.updateAppearanceSettings {
copy(colorSeed = selectedColor.value)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private fun LocalFilterEditDialog(
}) {
Icon(
Icons.Default.Check,
contentDescription = stringResource(id = R.string.done),
contentDescription = stringResource(id = android.R.string.ok),
)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ internal fun BlueskyReportStatusDialog(
}
},
) {
Text(text = stringResource(id = R.string.confirm))
Text(text = stringResource(id = android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onBack) {
Text(text = stringResource(id = R.string.cancel))
Text(text = stringResource(id = android.R.string.cancel))
}
},
title = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ fun DeleteStatusConfirmDialog(
onBack.invoke()
},
) {
Text(text = stringResource(id = R.string.confirm))
Text(text = stringResource(id = android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onBack) {
Text(text = stringResource(id = R.string.cancel))
Text(text = stringResource(id = android.R.string.cancel))
}
},
title = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ fun MastodonReportDialog(
onBack.invoke()
},
) {
Text(stringResource(R.string.confirm))
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(
onClick = onBack,
) {
Text(stringResource(R.string.cancel))
Text(stringResource(android.R.string.cancel))
}
},
title = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fun MisskeyReportDialog(
},
) {
Text(
text = stringResource(R.string.confirm),
text = stringResource(android.R.string.ok),
)
}
},
Expand All @@ -91,7 +91,7 @@ fun MisskeyReportDialog(
onClick = onBack,
) {
Text(
text = stringResource(R.string.cancel),
text = stringResource(android.R.string.cancel),
)
}
},
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/dev/dimension/flare/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import com.materialkolor.rememberDynamicColorScheme
import dev.dimension.flare.data.model.LocalAppearanceSettings
import dev.dimension.flare.data.model.Theme

Expand All @@ -29,15 +31,16 @@ fun FlareTheme(
dynamicColor: Boolean = LocalAppearanceSettings.current.dynamicTheme,
content: @Composable () -> Unit,
) {
val seed = Color(LocalAppearanceSettings.current.colorSeed)
val colorScheme =
when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}

darkTheme -> DarkColorScheme
else -> LightColorScheme
darkTheme -> rememberDynamicColorScheme(seed, true)
else -> rememberDynamicColorScheme(seed, false)
}
val view = LocalView.current
if (!view.isInEditMode && view.context is Activity) {
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

<string name="login_button">Login</string>
<string name="navigate_back">Navigate back</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>

<string name="home_tab_home_title">Home</string>
<string name="home_tab_notifications_title">Notifications</string>
Expand Down Expand Up @@ -248,6 +246,8 @@
<string name="settings_appearance_show_link_previews_description">Show link previews in the status</string>
<string name="settings_appearance_compat_link_previews">Compat link previews</string>
<string name="settings_appearance_compat_link_previews_description">Show link previews in compat mode in the status</string>
<string name="settings_appearance_theme_color">Theme color</string>
<string name="settings_appearance_theme_color_description">Change the theme color of the app</string>

<string name="settings_appearance_mastodon_show_visibility">Show visibility</string>
<string name="settings_appearance_mastodon_show_visibility_description">Show visibility in the status</string>
Expand Down Expand Up @@ -302,7 +302,6 @@
<string name="local_filter_for_search">Filter in search</string>
<string name="local_filter_delete">Delete Filter</string>

<string name="done">Done</string>
<string name="delete">Delete</string>

<string name="status_detail_translate">Translate to %1$s</string>
Expand Down
Loading

0 comments on commit ddac7ee

Please sign in to comment.