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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
implementation(projects.feature.foxFeatureApi)
implementation(projects.feature.foxFeatureImpl)
implementation(projects.core)
implementation(projects.data)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
Expand Down
17 changes: 15 additions & 2 deletions app/src/main/java/com/featuremodule/template/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.animation.doOnEnd
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.featuremodule.core.ui.theme.AppTheme
import com.featuremodule.template.ui.AppContent
import com.featuremodule.template.ui.ThemeState
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableStateFlow

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

setContent {
AppTheme {
AppContent(updateLoadedState = { isLoaded.value = it })
var theme by remember { mutableStateOf(ThemeState()) }
AppTheme(
colorsLight = theme.colorsLight,
colorsDark = theme.colorsDark,
themeStyle = theme.themeStyle,
) {
AppContent(
updateLoadedState = { isLoaded.value = it },
updateTheme = { theme = it },
)
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions app/src/main/java/com/featuremodule/template/ui/AppContent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -21,8 +20,9 @@ import com.featuremodule.core.util.CollectWithLifecycle

@Composable
internal fun AppContent(
viewModel: MainVM = hiltViewModel(),
updateLoadedState: (isLoaded: Boolean) -> Unit,
updateTheme: (ThemeState) -> Unit,
viewModel: MainVM = hiltViewModel(),
) {
val navController = rememberNavController()
val backStackEntry by navController.currentBackStackEntryAsState()
Expand All @@ -33,6 +33,10 @@ internal fun AppContent(
updateLoadedState(state.isLoaded)
}

LaunchedEffect(state.theme, updateTheme) {
updateTheme(state.theme)
}

state.commands.CollectWithLifecycle {
navController.handleCommand(it)
}
Expand All @@ -46,9 +50,8 @@ internal fun AppContent(
currentDestination = backStackEntry?.destination,
)
},
contentWindowInsets = WindowInsets(0),
// Remove this and status bar coloring in AppTheme for edge to edge
modifier = Modifier.windowInsetsPadding(WindowInsets.statusBars),
// Remove this for edge to edge
contentWindowInsets = WindowInsets.statusBars,
) { innerPadding ->
AppNavHost(
navController = navController,
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/featuremodule/template/ui/MainContract.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package com.featuremodule.template.ui

import androidx.compose.material3.ColorScheme
import com.featuremodule.core.navigation.NavCommand
import com.featuremodule.core.ui.UiEvent
import com.featuremodule.core.ui.UiState
import com.featuremodule.core.ui.theme.ColorsDark
import com.featuremodule.core.ui.theme.ColorsLight
import com.featuremodule.core.ui.theme.ThemeStyle
import kotlinx.coroutines.flow.SharedFlow

internal data class State(
val commands: SharedFlow<NavCommand>,
val isLoaded: Boolean = false,
val theme: ThemeState = ThemeState(),
) : UiState

internal data class ThemeState(
val colorsLight: ColorScheme = ColorsLight.Default.scheme,
val colorsDark: ColorScheme = ColorsDark.Default.scheme,
val themeStyle: ThemeStyle = ThemeStyle.System,
)

internal sealed interface Event : UiEvent {
data class OpenNavBarRoute(val route: String, val isSelected: Boolean) : Event
}
19 changes: 17 additions & 2 deletions app/src/main/java/com/featuremodule/template/ui/MainVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,35 @@ package com.featuremodule.template.ui
import com.featuremodule.core.navigation.NavCommand
import com.featuremodule.core.navigation.NavManager
import com.featuremodule.core.ui.BaseVM
import com.featuremodule.core.ui.theme.ColorsDark
import com.featuremodule.core.ui.theme.ColorsLight
import com.featuremodule.core.ui.theme.ThemeStyle
import com.featuremodule.data.prefs.ThemePreferences
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
internal class MainVM @Inject constructor(
private val navManager: NavManager,
private val themePreferences: ThemePreferences,
) : BaseVM<State, Event>() {
init {
launch {
// Do something useful before loading
setState { copy(isLoaded = true) }
// The only loading for now is theme loading, so isLoaded is set together
themePreferences.themeModelFlow.collect {
setState { copy(theme = it.toThemeState(), isLoaded = true) }
}
}
}

private fun ThemePreferences.ThemeModel.toThemeState() = ThemeState(
colorsLight = ColorsLight.entries.find { it.name == lightTheme }?.scheme
?: ColorsLight.Default.scheme,
colorsDark = ColorsDark.entries.find { it.name == darkTheme }?.scheme
?: ColorsDark.Default.scheme,
themeStyle = ThemeStyle.entries.find { it.name == themeStyle } ?: ThemeStyle.System,
)

override fun initialState() = State(navManager.commands)

override fun handleEvent(event: Event) {
Expand Down
48 changes: 28 additions & 20 deletions core/src/main/java/com/featuremodule/core/ui/theme/AppTheme.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
package com.featuremodule.core.ui.theme

import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable

private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80,
)

private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,
)
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

@Composable
fun AppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val colorScheme = if (darkTheme) {
DarkColorScheme
} else {
LightColorScheme
fun AppTheme(
colorsLight: ColorScheme,
colorsDark: ColorScheme,
themeStyle: ThemeStyle,
content: @Composable () -> Unit,
) {
val isStyleDark = when (themeStyle) {
ThemeStyle.Light -> false
ThemeStyle.Dark -> true
ThemeStyle.System -> isSystemInDarkTheme()
}
val colorScheme = if (isStyleDark) colorsDark else colorsLight

val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
WindowCompat.getInsetsController(window, view)
.isAppearanceLightStatusBars = !isStyleDark
WindowCompat.getInsetsController(window, view)
.isAppearanceLightNavigationBars = !isStyleDark
}
}

ProvideAppColors(darkTheme) {
ProvideAppColors(isStyleDark) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
Expand Down
11 changes: 0 additions & 11 deletions core/src/main/java/com/featuremodule/core/ui/theme/Color.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ val LocalAppColors = staticCompositionLocalOf { AppColors() }
* Draft for providing own color hierarchy to be used in the same way as MaterialTheme.
*/
data class AppColors(
val primary: Color = Purple40,
val secondary: Color = PurpleGrey40,
val tertiary: Color = Pink40,
val primary: Color = ColorsLight.Default.scheme.primary,
val secondary: Color = ColorsLight.Default.scheme.secondary,
val tertiary: Color = ColorsLight.Default.scheme.tertiary,
)

private val LightAppColors = AppColors()

private val DarkAppColors = AppColors(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80,
primary = ColorsDark.Default.scheme.primary,
secondary = ColorsDark.Default.scheme.secondary,
tertiary = ColorsDark.Default.scheme.tertiary,
)

@Composable
Expand Down
Loading
Loading