Skip to content

Commit

Permalink
Merge pull request #170 from oguzsout/feature/AnimatableThemeSwitch
Browse files Browse the repository at this point in the history
Feature/animatable theme switch
  • Loading branch information
oguzsout authored Dec 4, 2024
2 parents d27bb08 + dc2b006 commit af97696
Show file tree
Hide file tree
Showing 20 changed files with 443 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import androidx.datastore.preferences.preferencesDataStore
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.HOME_IMAGE_ROTATE_KEY
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.LANGUAGE_KEY
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.ONBOARDING
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.SHORTCUT_THEME
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.THEME_KEY
import com.oguzdogdu.walliescompose.domain.repository.AppSettingsRepository
import com.oguzdogdu.walliescompose.util.ThemeKeys
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
Expand All @@ -33,6 +35,10 @@ private val Context.onboardingVisibility: DataStore<Preferences> by preferencesD
name = ONBOARDING
)

private val Context.shortcutTheme: DataStore<Preferences> by preferencesDataStore(
name = SHORTCUT_THEME
)

class AppSettingsRepositoryImpl @Inject constructor(
private val context: Context,
) : AppSettingsRepository {
Expand All @@ -46,8 +52,9 @@ class AppSettingsRepositoryImpl @Inject constructor(
override suspend fun getThemeStrings(key: String): Flow<String?> {
return flow {
val preferencesKey = stringPreferencesKey(key)
val preference = context.themeDataStore.data.first()
emit(preference[preferencesKey])
context.themeDataStore.data.collect {
emit(it[preferencesKey])
}
}
}

Expand Down Expand Up @@ -97,10 +104,27 @@ class AppSettingsRepositoryImpl @Inject constructor(
}
}

override suspend fun putShorcutThemes(key: String, value: Boolean) {
val preferencesKey = booleanPreferencesKey(key)
context.shortcutTheme.edit {
it[preferencesKey] = value
}
}

override fun getShortcutThemes(key: String): Flow<Boolean> {
return flow {
val preferencesKey = booleanPreferencesKey(key)
context.shortcutTheme.data.collect { data ->
data[preferencesKey]?.let { emit(it) }
}
}
}

companion object {
const val THEME_KEY = "THEME_KEY"
const val LANGUAGE_KEY = "LANGUAGE_KEY"
const val HOME_IMAGE_ROTATE_KEY = "HOME_IMAGE_ROTATE_KEY"
const val ONBOARDING = "ONBOARDING"
const val SHORTCUT_THEME = "SHORTCUT_THEME"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ interface AppSettingsRepository {
fun getHomeRotateCardVisibility(key: String): Flow<Boolean>
suspend fun putOnboardingShow(key: String, value: Boolean)
fun getOnboardingShow(key: String): Flow<Boolean>
suspend fun putShorcutThemes(key: String, value: Boolean)
fun getShortcutThemes(key: String): Flow<Boolean>

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fun WalliesApp(
}
SharedTransitionLayout {
Scaffold(
modifier = Modifier.fillMaxSize().skipToLookaheadSize(),
modifier = Modifier.fillMaxSize(),
bottomBar = {
WalliesBottomAppBar(
navController = navController,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
Expand All @@ -53,6 +55,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.lifecycle.compose.LifecycleStartStopEffectScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import coil.compose.SubcomposeAsyncImage
Expand All @@ -61,9 +65,11 @@ import com.oguzdogdu.walliescompose.data.common.ImageLoadingState
import com.oguzdogdu.walliescompose.domain.model.popular.PopularImage
import com.oguzdogdu.walliescompose.domain.model.topics.Topics
import com.oguzdogdu.walliescompose.features.home.components.AnimatedImageRotationCard
import com.oguzdogdu.walliescompose.features.home.components.DayNightSwitch
import com.oguzdogdu.walliescompose.features.home.components.HomeRandomPage
import com.oguzdogdu.walliescompose.features.home.event.HomeScreenEvent
import com.oguzdogdu.walliescompose.features.home.state.HomeUIState
import com.oguzdogdu.walliescompose.features.settings.ThemeValues
import com.oguzdogdu.walliescompose.navigation.utils.WalliesIcons
import com.oguzdogdu.walliescompose.ui.theme.medium
import kotlinx.coroutines.coroutineScope
Expand Down Expand Up @@ -91,6 +97,14 @@ fun SharedTransitionScope.HomeScreenRoute(
var animatedImageRotateCardVisibility by remember {
mutableStateOf(false)
}
var themeCheck by remember {
mutableStateOf(false)
}
val shortcutTheme by viewModel.shortcutTheme.collectAsStateWithLifecycle()

LaunchedEffect (homeUiState.appTheme){
themeCheck = homeUiState.appTheme == ThemeValues.DARK_MODE.title
}

LaunchedEffect(Unit) {
viewModel.handleScreenEvents(HomeScreenEvent.FetchMainScreenUserData)
Expand Down Expand Up @@ -164,6 +178,17 @@ fun SharedTransitionScope.HomeScreenRoute(
)
}
}
if (shortcutTheme) {
DayNightSwitch(isNightMode = themeCheck) {
themeCheck = it
if(it) {
viewModel.setTheme(ThemeValues.DARK_MODE.title)
} else {
viewModel.setTheme(ThemeValues.LIGHT_MODE.title)
}
}

}

IconButton(
onClick = { onSearchClick.invoke() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.oguzdogdu.walliescompose.WalliesApplication
import com.oguzdogdu.walliescompose.data.common.takeListOr
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.HOME_IMAGE_ROTATE_KEY
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.SHORTCUT_THEME
import com.oguzdogdu.walliescompose.data.repository.AppSettingsRepositoryImpl.Companion.THEME_KEY
import com.oguzdogdu.walliescompose.domain.model.popular.PopularImage
import com.oguzdogdu.walliescompose.domain.model.random.RandomImage
import com.oguzdogdu.walliescompose.domain.model.topics.Topics
Expand All @@ -26,7 +28,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
Expand All @@ -40,6 +41,12 @@ class HomeViewModel @Inject constructor(
private val appSettingsRepository: AppSettingsRepository
) : ViewModel() {

val shortcutTheme: StateFlow<Boolean> =
appSettingsRepository.getShortcutThemes(SHORTCUT_THEME).stateIn(
viewModelScope,
SharingStarted.Eagerly, false
)

private val _homeListState = MutableStateFlow<HomeUIState>(HomeUIState())
val homeListState = _homeListState.asStateFlow()

Expand All @@ -57,7 +64,10 @@ class HomeViewModel @Inject constructor(
fun handleScreenEvents(event: HomeScreenEvent) {
when (event) {
HomeScreenEvent.FetchHomeScreenLists -> fetchHomeScreenData()
HomeScreenEvent.FetchMainScreenUserData -> checkUserAuthState()
HomeScreenEvent.FetchMainScreenUserData -> {
checkUserAuthState()
fetchTheme()
}
}
}

Expand All @@ -71,6 +81,22 @@ class HomeViewModel @Inject constructor(
}
}

fun setTheme(value: String) {
viewModelScope.launch {
appSettingsRepository.putThemeStrings(key = THEME_KEY, value = value)
application.theme.value = value
}
}
private fun fetchTheme() {
viewModelScope.launch {
appSettingsRepository.getThemeStrings(key = THEME_KEY).collectLatest { theme ->
_homeListState.update {
it.copy(appTheme = theme)
}
}
}
}

private fun combineResults(
randomResult: Flow<Resource<List<RandomImage>?>>,
topicsResult: Flow<Resource<List<Topics>?>>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.oguzdogdu.walliescompose.features.home.components

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.oguzdogdu.walliescompose.R
import com.oguzdogdu.walliescompose.util.noRippleClickable

@Composable
fun DayNightSwitch(
isNightMode: Boolean,
onToggle: (Boolean) -> Unit
) {
val transition = updateTransition(targetState = isNightMode, label = "DayNightTransition")

val circleOffset by transition.animateDp(
label = "CircleOffset",
transitionSpec = { tween(500, easing = LinearEasing) }
) { if (it) 8.dp else 32.dp }

val circleColor by transition.animateColor(
label = "CircleColor"
) { if (it) Color(0xFFFFC107) else Color(0xFFFF9800) }

Box(
modifier = Modifier
.size(56.dp, 32.dp)
.border(width = 2.dp, shape = RoundedCornerShape(16.dp), color = Color.Gray)
.noRippleClickable { onToggle(!isNightMode) },
contentAlignment = Alignment.CenterStart
) {
AnimatedVisibility(
visible = isNightMode,
modifier = Modifier
.align(Alignment.CenterEnd),
enter = slideInVertically(tween(500)),
exit = slideOutVertically(tween(500))

) {
Icon(
painter = painterResource(id = R.drawable.ic_sun),
contentDescription = "Night Icon",
modifier = Modifier
.size(20.dp)
.padding(end = 6.dp)
.align(Alignment.Center)
)
}

AnimatedVisibility(
visible = !isNightMode, modifier = Modifier
.align(Alignment.CenterStart),
enter = slideInVertically(tween(500)),
exit = slideOutVertically(tween(500))
) {
Icon(
painter = painterResource(id = R.drawable.ic_moon),
contentDescription = "Day Icon",
modifier = Modifier
.size(24.dp)
.padding(start = 6.dp)
.align(Alignment.Center)

)
}

Box(
modifier = Modifier
.size(16.dp)
.offset {
IntOffset(
circleOffset
.toPx()
.toInt(), 0
)
}
.background(circleColor, shape = CircleShape)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.oguzdogdu.walliescompose.features.home.state

import androidx.compose.runtime.Stable
import com.oguzdogdu.walliescompose.domain.model.popular.PopularImage
import com.oguzdogdu.walliescompose.domain.model.random.RandomImage
import com.oguzdogdu.walliescompose.domain.model.topics.Topics

data class HomeUIState(
data class HomeUIState(
val loading: Boolean = false,
val errorMessage: String = "",
val random: List<RandomImage> = emptyList(),
val topics: List<Topics> = emptyList(),
val popular: List<PopularImage> = emptyList(),
val appTheme: String? = null
) {
fun isEmpty(): Boolean = topics.isEmpty() and popular.isEmpty()
}
Expand Down
Loading

0 comments on commit af97696

Please sign in to comment.