Skip to content

Commit cf15a80

Browse files
authored
Add themes (#9)
* Adding ThemePreferences, slightly reworking colors in AppTheme.kt * Adding theme changes to AppTheme, renaming some variables * Another preferences change * Base ChooseThemeScreen and navigation to it * Added new themes * Added preview for chosen themes * Added SaveOrCloseDialog, fixed few issues * Added save button * Added ThemeStyleChooser * Ktling reformat, some code and theme fixes, and small changes * Detekt fixes
1 parent d8ae35f commit cf15a80

File tree

19 files changed

+794
-46
lines changed

19 files changed

+794
-46
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ dependencies {
8181
implementation(projects.feature.foxFeatureApi)
8282
implementation(projects.feature.foxFeatureImpl)
8383
implementation(projects.core)
84+
implementation(projects.data)
8485

8586
implementation(libs.androidx.core.ktx)
8687
implementation(libs.androidx.core.splashscreen)

app/src/main/java/com/featuremodule/template/MainActivity.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ import androidx.activity.ComponentActivity
1010
import androidx.activity.SystemBarStyle
1111
import androidx.activity.compose.setContent
1212
import androidx.activity.enableEdgeToEdge
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.runtime.setValue
1317
import androidx.core.animation.doOnEnd
1418
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1519
import com.featuremodule.core.ui.theme.AppTheme
1620
import com.featuremodule.template.ui.AppContent
21+
import com.featuremodule.template.ui.ThemeState
1722
import dagger.hilt.android.AndroidEntryPoint
1823
import kotlinx.coroutines.flow.MutableStateFlow
1924

@@ -29,8 +34,16 @@ class MainActivity : ComponentActivity() {
2934
enableEdgeToEdge(statusBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT))
3035

3136
setContent {
32-
AppTheme {
33-
AppContent(updateLoadedState = { isLoaded.value = it })
37+
var theme by remember { mutableStateOf(ThemeState()) }
38+
AppTheme(
39+
colorsLight = theme.colorsLight,
40+
colorsDark = theme.colorsDark,
41+
themeStyle = theme.themeStyle,
42+
) {
43+
AppContent(
44+
updateLoadedState = { isLoaded.value = it },
45+
updateTheme = { theme = it },
46+
)
3447
}
3548
}
3649
}

app/src/main/java/com/featuremodule/template/ui/AppContent.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.WindowInsets
44
import androidx.compose.foundation.layout.consumeWindowInsets
55
import androidx.compose.foundation.layout.padding
66
import androidx.compose.foundation.layout.statusBars
7-
import androidx.compose.foundation.layout.windowInsetsPadding
87
import androidx.compose.material3.Scaffold
98
import androidx.compose.runtime.Composable
109
import androidx.compose.runtime.LaunchedEffect
@@ -21,8 +20,9 @@ import com.featuremodule.core.util.CollectWithLifecycle
2120

2221
@Composable
2322
internal fun AppContent(
24-
viewModel: MainVM = hiltViewModel(),
2523
updateLoadedState: (isLoaded: Boolean) -> Unit,
24+
updateTheme: (ThemeState) -> Unit,
25+
viewModel: MainVM = hiltViewModel(),
2626
) {
2727
val navController = rememberNavController()
2828
val backStackEntry by navController.currentBackStackEntryAsState()
@@ -33,6 +33,10 @@ internal fun AppContent(
3333
updateLoadedState(state.isLoaded)
3434
}
3535

36+
LaunchedEffect(state.theme, updateTheme) {
37+
updateTheme(state.theme)
38+
}
39+
3640
state.commands.CollectWithLifecycle {
3741
navController.handleCommand(it)
3842
}
@@ -46,9 +50,8 @@ internal fun AppContent(
4650
currentDestination = backStackEntry?.destination,
4751
)
4852
},
49-
contentWindowInsets = WindowInsets(0),
50-
// Remove this and status bar coloring in AppTheme for edge to edge
51-
modifier = Modifier.windowInsetsPadding(WindowInsets.statusBars),
53+
// Remove this for edge to edge
54+
contentWindowInsets = WindowInsets.statusBars,
5255
) { innerPadding ->
5356
AppNavHost(
5457
navController = navController,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
package com.featuremodule.template.ui
22

3+
import androidx.compose.material3.ColorScheme
34
import com.featuremodule.core.navigation.NavCommand
45
import com.featuremodule.core.ui.UiEvent
56
import com.featuremodule.core.ui.UiState
7+
import com.featuremodule.core.ui.theme.ColorsDark
8+
import com.featuremodule.core.ui.theme.ColorsLight
9+
import com.featuremodule.core.ui.theme.ThemeStyle
610
import kotlinx.coroutines.flow.SharedFlow
711

812
internal data class State(
913
val commands: SharedFlow<NavCommand>,
1014
val isLoaded: Boolean = false,
15+
val theme: ThemeState = ThemeState(),
1116
) : UiState
1217

18+
internal data class ThemeState(
19+
val colorsLight: ColorScheme = ColorsLight.Default.scheme,
20+
val colorsDark: ColorScheme = ColorsDark.Default.scheme,
21+
val themeStyle: ThemeStyle = ThemeStyle.System,
22+
)
23+
1324
internal sealed interface Event : UiEvent {
1425
data class OpenNavBarRoute(val route: String, val isSelected: Boolean) : Event
1526
}

app/src/main/java/com/featuremodule/template/ui/MainVM.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,35 @@ package com.featuremodule.template.ui
33
import com.featuremodule.core.navigation.NavCommand
44
import com.featuremodule.core.navigation.NavManager
55
import com.featuremodule.core.ui.BaseVM
6+
import com.featuremodule.core.ui.theme.ColorsDark
7+
import com.featuremodule.core.ui.theme.ColorsLight
8+
import com.featuremodule.core.ui.theme.ThemeStyle
9+
import com.featuremodule.data.prefs.ThemePreferences
610
import dagger.hilt.android.lifecycle.HiltViewModel
711
import javax.inject.Inject
812

913
@HiltViewModel
1014
internal class MainVM @Inject constructor(
1115
private val navManager: NavManager,
16+
private val themePreferences: ThemePreferences,
1217
) : BaseVM<State, Event>() {
1318
init {
1419
launch {
15-
// Do something useful before loading
16-
setState { copy(isLoaded = true) }
20+
// The only loading for now is theme loading, so isLoaded is set together
21+
themePreferences.themeModelFlow.collect {
22+
setState { copy(theme = it.toThemeState(), isLoaded = true) }
23+
}
1724
}
1825
}
1926

27+
private fun ThemePreferences.ThemeModel.toThemeState() = ThemeState(
28+
colorsLight = ColorsLight.entries.find { it.name == lightTheme }?.scheme
29+
?: ColorsLight.Default.scheme,
30+
colorsDark = ColorsDark.entries.find { it.name == darkTheme }?.scheme
31+
?: ColorsDark.Default.scheme,
32+
themeStyle = ThemeStyle.entries.find { it.name == themeStyle } ?: ThemeStyle.System,
33+
)
34+
2035
override fun initialState() = State(navManager.commands)
2136

2237
override fun handleEvent(event: Event) {

core/src/main/java/com/featuremodule/core/ui/theme/AppTheme.kt

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
package com.featuremodule.core.ui.theme
22

3+
import android.app.Activity
34
import androidx.compose.foundation.isSystemInDarkTheme
5+
import androidx.compose.material3.ColorScheme
46
import androidx.compose.material3.MaterialTheme
5-
import androidx.compose.material3.darkColorScheme
6-
import androidx.compose.material3.lightColorScheme
77
import androidx.compose.runtime.Composable
88
import androidx.compose.runtime.ReadOnlyComposable
9-
10-
private val DarkColorScheme = darkColorScheme(
11-
primary = Purple80,
12-
secondary = PurpleGrey80,
13-
tertiary = Pink80,
14-
)
15-
16-
private val LightColorScheme = lightColorScheme(
17-
primary = Purple40,
18-
secondary = PurpleGrey40,
19-
tertiary = Pink40,
20-
)
9+
import androidx.compose.runtime.SideEffect
10+
import androidx.compose.ui.platform.LocalView
11+
import androidx.core.view.WindowCompat
2112

2213
@Composable
23-
fun AppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
24-
val colorScheme = if (darkTheme) {
25-
DarkColorScheme
26-
} else {
27-
LightColorScheme
14+
fun AppTheme(
15+
colorsLight: ColorScheme,
16+
colorsDark: ColorScheme,
17+
themeStyle: ThemeStyle,
18+
content: @Composable () -> Unit,
19+
) {
20+
val isStyleDark = when (themeStyle) {
21+
ThemeStyle.Light -> false
22+
ThemeStyle.Dark -> true
23+
ThemeStyle.System -> isSystemInDarkTheme()
24+
}
25+
val colorScheme = if (isStyleDark) colorsDark else colorsLight
26+
27+
val view = LocalView.current
28+
if (!view.isInEditMode) {
29+
SideEffect {
30+
val window = (view.context as Activity).window
31+
WindowCompat.getInsetsController(window, view)
32+
.isAppearanceLightStatusBars = !isStyleDark
33+
WindowCompat.getInsetsController(window, view)
34+
.isAppearanceLightNavigationBars = !isStyleDark
35+
}
2836
}
2937

30-
ProvideAppColors(darkTheme) {
38+
ProvideAppColors(isStyleDark) {
3139
MaterialTheme(
3240
colorScheme = colorScheme,
3341
typography = Typography,

core/src/main/java/com/featuremodule/core/ui/theme/Color.kt

Lines changed: 0 additions & 11 deletions
This file was deleted.

core/src/main/java/com/featuremodule/core/ui/theme/LocalAppColors.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ val LocalAppColors = staticCompositionLocalOf { AppColors() }
1313
* Draft for providing own color hierarchy to be used in the same way as MaterialTheme.
1414
*/
1515
data class AppColors(
16-
val primary: Color = Purple40,
17-
val secondary: Color = PurpleGrey40,
18-
val tertiary: Color = Pink40,
16+
val primary: Color = ColorsLight.Default.scheme.primary,
17+
val secondary: Color = ColorsLight.Default.scheme.secondary,
18+
val tertiary: Color = ColorsLight.Default.scheme.tertiary,
1919
)
2020

2121
private val LightAppColors = AppColors()
2222

2323
private val DarkAppColors = AppColors(
24-
primary = Purple80,
25-
secondary = PurpleGrey80,
26-
tertiary = Pink80,
24+
primary = ColorsDark.Default.scheme.primary,
25+
secondary = ColorsDark.Default.scheme.secondary,
26+
tertiary = ColorsDark.Default.scheme.tertiary,
2727
)
2828

2929
@Composable

0 commit comments

Comments
 (0)