Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a setting for changing app's theme #179

Merged
merged 2 commits into from
Jul 15, 2022
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ android {
}

dependencies {
implementation(project(deps.local.domain))
implementation(project(deps.local.core))
implementation(project(deps.local.commonsUi))
implementation(project(deps.local.commonsUiWidgets))
Expand All @@ -50,6 +51,7 @@ dependencies {
implementation(project(deps.local.featureLikes))
implementation(project(deps.local.featureNews))
implementation(project(deps.local.featureSearch))
implementation(project(deps.local.featureSettings))

implementation(deps.androidX.splash)

Expand Down
70 changes: 40 additions & 30 deletions app/src/main/java/com/paulrybitskyi/gamedge/AppNavigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.paulrybitskyi.gamedge.feature.news.GamingNewsRoute
import com.paulrybitskyi.gamedge.feature.news.widgets.GamingNews
import com.paulrybitskyi.gamedge.feature.search.GamesSearch
import com.paulrybitskyi.gamedge.feature.search.GamesSearchRoute
import com.paulrybitskyi.gamedge.feature.settings.Settings

@Composable
internal fun AppNavigation(
Expand All @@ -54,133 +55,142 @@ internal fun AppNavigation(
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None },
) {
newsScreen(
discoverScreen(
navController = navController,
modifier = modifier,
)
discoverScreen(
likesScreen(
navController = navController,
modifier = modifier,
)
likesScreen(
newsScreen(
navController = navController,
modifier = modifier,
)
gamesSearchScreen(navController)
gamesCategoryScreen(navController)
gameInfoScreen(navController)
imageViewerScreen(navController)
settingsScreen(modifier = modifier)
gamesSearchScreen(navController = navController)
gamesCategoryScreen(navController = navController)
gameInfoScreen(navController = navController)
imageViewerScreen(navController = navController)
}
}

private fun NavGraphBuilder.newsScreen(
private fun NavGraphBuilder.discoverScreen(
navController: NavHostController,
modifier: Modifier,
) {
composable(
route = Screen.News.route,
route = Screen.Discover.route,
enterTransition = { null },
exitTransition = {
when (Screen.forRoute(targetState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.exit()
Screen.GamesCategory,
Screen.GameInfo -> HorizontalSliding.exit()
else -> null
}
},
popEnterTransition = {
when (Screen.forRoute(initialState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.popEnter()
Screen.GamesCategory,
Screen.GameInfo -> HorizontalSliding.popEnter()
else -> null
}
},
popExitTransition = { null },
) {
GamingNews(modifier) { route ->
GamesDiscovery(modifier) { route ->
when (route) {
is GamingNewsRoute.Search -> {
is GamesDiscoveryRoute.Search -> {
navController.navigate(Screen.GamesSearch.route)
}
is GamesDiscoveryRoute.Category -> {
navController.navigate(Screen.GamesCategory.createLink(route.category))
}
is GamesDiscoveryRoute.Info -> {
navController.navigate(Screen.GameInfo.createLink(route.gameId))
}
}
}
}
}

private fun NavGraphBuilder.discoverScreen(
private fun NavGraphBuilder.likesScreen(
navController: NavHostController,
modifier: Modifier,
) {
composable(
route = Screen.Discover.route,
route = Screen.Likes.route,
enterTransition = { null },
exitTransition = {
when (Screen.forRoute(targetState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.exit()
Screen.GamesCategory,
Screen.GameInfo -> HorizontalSliding.exit()
else -> null
}
},
popEnterTransition = {
when (Screen.forRoute(initialState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.popEnter()
Screen.GamesCategory,
Screen.GameInfo -> HorizontalSliding.popEnter()
else -> null
}
},
popExitTransition = { null },
) {
GamesDiscovery(modifier) { route ->
LikedGames(modifier) { route ->
when (route) {
is GamesDiscoveryRoute.Search -> {
is LikedGamesRoute.Search -> {
navController.navigate(Screen.GamesSearch.route)
}
is GamesDiscoveryRoute.Category -> {
navController.navigate(Screen.GamesCategory.createLink(route.category))
}
is GamesDiscoveryRoute.Info -> {
is LikedGamesRoute.Info -> {
navController.navigate(Screen.GameInfo.createLink(route.gameId))
}
}
}
}
}

private fun NavGraphBuilder.likesScreen(
private fun NavGraphBuilder.newsScreen(
navController: NavHostController,
modifier: Modifier,
) {
composable(
route = Screen.Likes.route,
route = Screen.News.route,
enterTransition = { null },
exitTransition = {
when (Screen.forRoute(targetState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.exit()
Screen.GameInfo -> HorizontalSliding.exit()
else -> null
}
},
popEnterTransition = {
when (Screen.forRoute(initialState.destination.requireRoute())) {
Screen.GamesSearch -> OvershootScaling.popEnter()
Screen.GameInfo -> HorizontalSliding.popEnter()
else -> null
}
},
popExitTransition = { null },
) {
LikedGames(modifier) { route ->
GamingNews(modifier) { route ->
when (route) {
is LikedGamesRoute.Search -> {
is GamingNewsRoute.Search -> {
navController.navigate(Screen.GamesSearch.route)
}
is LikedGamesRoute.Info -> {
navController.navigate(Screen.GameInfo.createLink(route.gameId))
}
}
}
}
}

private fun NavGraphBuilder.settingsScreen(modifier: Modifier) {
composable(
route = Screen.Settings.route,
) {
Settings(modifier)
}
}

private fun NavGraphBuilder.gamesSearchScreen(navController: NavHostController) {
composable(
route = Screen.GamesSearch.route,
Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/com/paulrybitskyi/gamedge/BottomBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,6 @@ private enum class BottomNavigationItemModel(
val titleId: Int,
val screen: Screen,
) {
NEWS(
iconId = R.drawable.newspaper,
titleId = R.string.gaming_news_toolbar_title,
screen = Screen.News,
),
DISCOVER(
iconId = R.drawable.compass_rose,
titleId = R.string.games_discovery_toolbar_title,
Expand All @@ -135,4 +130,14 @@ private enum class BottomNavigationItemModel(
titleId = R.string.liked_games_toolbar_title,
screen = Screen.Likes,
),
NEWS(
iconId = R.drawable.newspaper,
titleId = R.string.gaming_news_toolbar_title,
screen = Screen.News,
),
SETTINGS(
iconId = R.drawable.cog_outline,
titleId = R.string.settings_toolbar_title,
screen = Screen.Settings,
),
}
53 changes: 50 additions & 3 deletions app/src/main/java/com/paulrybitskyi/gamedge/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.coroutineScope
import com.paulrybitskyi.commons.ktx.intentFor
import com.paulrybitskyi.gamedge.commons.ui.LocalNetworkStateProvider
import com.paulrybitskyi.gamedge.commons.ui.LocalTextSharer
Expand All @@ -32,7 +36,12 @@ import com.paulrybitskyi.gamedge.commons.ui.theme.GamedgeTheme
import com.paulrybitskyi.gamedge.core.providers.NetworkStateProvider
import com.paulrybitskyi.gamedge.core.sharers.TextSharer
import com.paulrybitskyi.gamedge.core.urlopener.UrlOpener
import com.paulrybitskyi.gamedge.domain.settings.entities.Settings
import com.paulrybitskyi.gamedge.domain.settings.entities.Theme
import com.paulrybitskyi.gamedge.domain.settings.usecases.ObserveThemeUseCase
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -49,24 +58,62 @@ class MainActivity : ComponentActivity() {
@Inject lateinit var textSharer: TextSharer
@Inject lateinit var networkStateProvider: NetworkStateProvider

override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
@Inject lateinit var observeThemeUseCase: ObserveThemeUseCase

private var appTheme: Theme? = null

override fun onCreate(savedInstanceState: Bundle?) {
setupSplashScreen()
super.onCreate(savedInstanceState)
loadAppTheme()
setupSystemBars()
setupCompose()
}

private fun setupSplashScreen() {
// Waiting until the app's theme is loaded first before
// displaying the dashboard to prevent the user from seeing
// the app blinking as a result of the theme change
installSplashScreen().setKeepOnScreenCondition {
appTheme == null
}
}

private fun loadAppTheme() {
lifecycle.coroutineScope.launch {
appTheme = observeThemeUseCase.execute().first()
}
}

private fun setupSystemBars() {
// To be able to draw behind system bars & change their colors
WindowCompat.setDecorFitsSystemWindows(window, false)
}

private fun setupCompose() {
setContent {
CompositionLocalProvider(LocalUrlOpener provides urlOpener) {
CompositionLocalProvider(LocalTextSharer provides textSharer) {
CompositionLocalProvider(LocalNetworkStateProvider provides networkStateProvider) {
GamedgeTheme {
GamedgeTheme(useDarkTheme = shouldUseDarkTheme()) {
MainScreen()
}
}
}
}
}
}

@Composable
private fun shouldUseDarkTheme(): Boolean {
val themeState = observeThemeUseCase.execute().collectAsState(
initial = Settings.DEFAULT.theme,
)

return when (themeState.value) {
Theme.LIGHT -> false
Theme.DARK -> true
Theme.SYSTEM -> isSystemInDarkTheme()
}
}
}
6 changes: 4 additions & 2 deletions app/src/main/java/com/paulrybitskyi/gamedge/Screen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import java.net.URLEncoder
internal val START_SCREEN = Screen.Discover

internal sealed class Screen(val route: String) {
object News : Screen("news")
object Discover : Screen("discover")
object Likes : Screen("likes")
object News : Screen("news")
object Settings : Screen("settings")
object GamesSearch : Screen("games-search")

object GamesCategory : Screen("games-category/{${Parameters.CATEGORY}}") {
Expand Down Expand Up @@ -98,9 +99,10 @@ internal sealed class Screen(val route: String) {

fun forRoute(route: String): Screen {
return when (route) {
News.route -> News
Discover.route -> Discover
Likes.route -> Likes
News.route -> News
Settings.route -> Settings
GamesSearch.route -> GamesSearch
GamesCategory.route -> GamesCategory
GameInfo.route -> GameInfo
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ object deps {
const val featureLikes = ":feature-likes"
const val featureNews = ":feature-news"
const val featureSearch = ":feature-search"
const val featureSettings = ":feature-settings"
}

object kotlin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.paulrybitskyi.gamedge.data.commons.DataPagination
import com.paulrybitskyi.gamedge.data.games.DataCategory
import com.paulrybitskyi.gamedge.data.games.DataCompany
import com.paulrybitskyi.gamedge.data.games.DataGame
import com.paulrybitskyi.gamedge.data.settings.DataSettings
import com.paulrybitskyi.gamedge.data.settings.DataTheme
import com.paulrybitskyi.gamedge.domain.articles.DomainArticle
import com.paulrybitskyi.gamedge.domain.articles.usecases.ObserveArticlesUseCase
import com.paulrybitskyi.gamedge.domain.articles.usecases.RefreshArticlesUseCase
Expand All @@ -42,6 +44,8 @@ import com.paulrybitskyi.gamedge.domain.games.usecases.RefreshCompanyDevelopedGa
import com.paulrybitskyi.gamedge.domain.games.usecases.RefreshSimilarGamesUseCase
import com.paulrybitskyi.gamedge.domain.games.usecases.likes.ObserveGameLikeStateUseCase
import com.paulrybitskyi.gamedge.domain.games.usecases.likes.ToggleGameLikeStateUseCase
import com.paulrybitskyi.gamedge.domain.settings.DomainSettings
import com.paulrybitskyi.gamedge.domain.settings.DomainTheme
import com.paulrybitskyi.gamedge.commons.api.Error as ApiError
import com.paulrybitskyi.gamedge.data.commons.entities.Error as DataError
import com.paulrybitskyi.gamedge.domain.commons.entities.Error as DomainError
Expand Down Expand Up @@ -169,6 +173,13 @@ val DATA_OAUTH_CREDENTIALS = DataOauthCredentials(
tokenTtl = 5000L,
)

val DOMAIN_SETTINGS = DomainSettings(
theme = DomainTheme.DARK,
)
val DATA_SETTINGS = DataSettings(
theme = DataTheme.DARK,
)

val DOMAIN_PAGINATION = DomainPagination(offset = 0, limit = 20)
val DATA_PAGINATION = DataPagination(offset = 0, limit = 20)

Expand Down
Loading