Skip to content

Commit

Permalink
Merge pull request #226 from JeonK1/feature/#147
Browse files Browse the repository at this point in the history
  • Loading branch information
wisemuji authored Aug 20, 2023
2 parents 5fd66ae + bc9665f commit 50feaf4
Show file tree
Hide file tree
Showing 20 changed files with 225 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ configureHiltAndroid()

dependencies {
implementation(project(":core:model"))
implementation(project(":core:data"))
implementation(project(":core:designsystem"))
implementation(project(":core:domain"))
implementation(project(":core:navigation"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.droidknights.app2023.configureCoroutineAndroid
import com.droidknights.app2023.configureHiltAndroid
import com.droidknights.app2023.configureKotest
import com.droidknights.app2023.configureKotlinAndroid

Expand All @@ -10,3 +11,4 @@ plugins {
configureKotlinAndroid()
configureKotest()
configureCoroutineAndroid()
configureHiltAndroid()
1 change: 1 addition & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ android {

dependencies {
implementation(projects.core.model)
implementation(projects.core.datastore)

implementation(libs.retrofit.core)
implementation(libs.retrofit.kotlin.serialization)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.droidknights.app2023.core.data.api.fake.AssetsGithubRawApi
import com.droidknights.app2023.core.data.repository.ContributorRepository
import com.droidknights.app2023.core.data.repository.DefaultContributorRepository
import com.droidknights.app2023.core.data.repository.DefaultSessionRepository
import com.droidknights.app2023.core.data.repository.DefaultSettingsRepository
import com.droidknights.app2023.core.data.repository.DefaultSponsorRepository
import com.droidknights.app2023.core.data.repository.SessionRepository
import com.droidknights.app2023.core.data.repository.SettingsRepository
import com.droidknights.app2023.core.data.repository.SponsorRepository
import dagger.Binds
import dagger.Module
Expand All @@ -26,6 +28,11 @@ internal abstract class DataModule {
repository: DefaultContributorRepository,
): ContributorRepository

@Binds
abstract fun bindsSettingsRepository(
repository: DefaultSettingsRepository,
): SettingsRepository

@InstallIn(SingletonComponent::class)
@Module
internal object FakeModule {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.droidknights.app2023.core.data.repository

import com.droidknights.app2023.core.datastore.SettingsPreferencesDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

internal class DefaultSettingsRepository @Inject constructor(
private val preferencesDataSource: SettingsPreferencesDataSource
) : SettingsRepository {

override fun getIsDarkTheme(): Flow<Boolean> =
preferencesDataSource.settingsData.map { settingsData -> settingsData.isDarkTheme }

override suspend fun updateIsDarkTheme(isDarkTheme: Boolean) {
preferencesDataSource.updateIsDarkTheme(isDarkTheme)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.droidknights.app2023.core.data.repository

import kotlinx.coroutines.flow.Flow

interface SettingsRepository {

fun getIsDarkTheme(): Flow<Boolean>

suspend fun updateIsDarkTheme(isDarkTheme: Boolean)
}
1 change: 1 addition & 0 deletions core/datastore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
13 changes: 13 additions & 0 deletions core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("droidknights.android.library")
}

android {
namespace = "com.droidknights.app2023.core.datastore"
}

dependencies {
testImplementation(libs.junit4)
testImplementation(libs.kotlin.test)
implementation(libs.androidx.datastore)
}
2 changes: 2 additions & 0 deletions core/datastore/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.droidknights.app2023.core.datastore

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import com.droidknights.app2023.core.datastore.model.SettingsData
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class SettingsPreferencesDataSource @Inject constructor(
private val dataStore: DataStore<Preferences>
) {
object PreferencesKey {
val IS_DARK_THEME = booleanPreferencesKey("IS_DARK_THEME")
}

val settingsData = dataStore.data.map { preferences ->
SettingsData(
isDarkTheme = preferences[PreferencesKey.IS_DARK_THEME] ?: false
)
}

suspend fun updateIsDarkTheme(isDarkTheme: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKey.IS_DARK_THEME] = isDarkTheme
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.droidknights.app2023.core.datastore.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
private const val DATASTORE_NAME = "SETTINGS_PREFERENCES"
private val Context.dataStore by preferencesDataStore(DATASTORE_NAME)

@Provides
@Singleton
fun provideSettingsDataStore(
@ApplicationContext context: Context
): DataStore<Preferences> = context.dataStore
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.droidknights.app2023.core.datastore.model

data class SettingsData(
val isDarkTheme: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.droidknights.app2023.core.datastore

import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import io.kotest.core.spec.style.StringSpec
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import org.junit.rules.TemporaryFolder

internal class SettingsPreferencesDataSourceTest : StringSpec() {

private lateinit var testDispatcher: TestDispatcher
private lateinit var tempFolder: TemporaryFolder
private lateinit var dataSource: SettingsPreferencesDataSource

init {
beforeSpec {
testDispatcher = StandardTestDispatcher()
tempFolder = TemporaryFolder.builder().assureDeletion().build()
dataSource = SettingsPreferencesDataSource(
PreferenceDataStoreFactory.create(
scope = CoroutineScope(testDispatcher),
produceFile = { tempFolder.newFile("SETTINGS_PREFERENCES_TEST") }
)
)
}

afterSpec {
tempFolder.delete()
}

"isDarkTheme 초기상태 테스트".config(true) {
CoroutineScope(testDispatcher).launch {
// Given - 초기상태

// When - dataSource 의 초기 SettingsData 값 조회
val settingsData = dataSource.settingsData.first()

// Then - SettingsData.isDarkTheme 값이 false 이어야 한다
assert(settingsData.isDarkTheme == false)
}
}

"isDarkTheme 저장 및 조회 테스트" {
CoroutineScope(testDispatcher).launch {
// Given - isDarkTheme is true
dataSource.updateIsDarkTheme(true)

// When - isDarkTheme 을 true 로 업데이트 후 SettingsData 값 조회
val settingsData = dataSource.settingsData.first()

// Then - SettingsData.isDarkTheme 값이 true 이어야 한다
assert(settingsData.isDarkTheme == true)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
package com.droidknights.app2023.feature.main

import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.droidknights.app2023.core.designsystem.theme.KnightsTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private var isDarkTheme by mutableStateOf(false)
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

WindowCompat.setDecorFitsSystemWindows(window, false)

setContent {
val isDarkTheme by viewModel.isDarkTheme.collectAsStateWithLifecycle(false, this)
KnightsTheme(darkTheme = isDarkTheme) {
MainScreen()
MainScreen(
onChangeDarkTheme = { isDarkTheme -> viewModel.updateIsDarkTheme(isDarkTheme) }
)
}
}
}

private fun isNightModeEnabled(uiMode: Int): Boolean {
val currentNightMode = uiMode and Configuration.UI_MODE_NIGHT_MASK
return currentNightMode == Configuration.UI_MODE_NIGHT_YES
}

// FIXME : configurationChanges를 사용하지 않고 깜빡이지 않게 테마를 바꾸는 방법 찾기
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
isDarkTheme = isNightModeEnabled(newConfig.uiMode)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ import com.droidknights.app2023.feature.session.navigation.sessionNavGraph
import com.droidknights.app2023.feature.setting.navigation.settingNavGraph

@Composable
internal fun MainScreen(navigator: MainNavigator = rememberMainNavigator()) {
internal fun MainScreen(
navigator: MainNavigator = rememberMainNavigator(),
onChangeDarkTheme: (Boolean) -> Unit
) {
Scaffold(
content = { padding ->
Box(
Expand All @@ -60,6 +63,7 @@ internal fun MainScreen(navigator: MainNavigator = rememberMainNavigator()) {
)
settingNavGraph(
padding = padding,
onChangeDarkTheme = onChangeDarkTheme
)

bookmarkNavGraph()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.droidknights.app2023.feature.main

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.droidknights.app2023.core.data.repository.SettingsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
) : ViewModel() {
val isDarkTheme = settingsRepository.getIsDarkTheme()

fun updateIsDarkTheme(isDarkTheme: Boolean) = viewModelScope.launch {
settingsRepository.updateIsDarkTheme(isDarkTheme)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.droidknights.app2023.feature.setting

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -35,7 +34,10 @@ import com.droidknights.app2023.core.designsystem.theme.KnightsTheme
import com.droidknights.app2023.core.designsystem.theme.LocalDarkTheme

@Composable
internal fun SettingScreen(padding: PaddingValues) {
internal fun SettingScreen(
padding: PaddingValues,
onChangeDarkTheme: (Boolean) -> Unit
) {
val scrollState = rememberScrollState()
Column(
Modifier
Expand All @@ -44,21 +46,17 @@ internal fun SettingScreen(padding: PaddingValues) {
.verticalScroll(scrollState),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
LightDarkThemeCard()
LightDarkThemeCard(
onChangeDarkTheme = onChangeDarkTheme
)
}
}

@Composable
private fun LightDarkThemeCard(darkTheme: Boolean = LocalDarkTheme.current) {
val changeDarkTheme: (Boolean) -> Unit = {
val mode = if (it) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(mode)
}

private fun LightDarkThemeCard(
onChangeDarkTheme: (Boolean) -> Unit,
darkTheme: Boolean = LocalDarkTheme.current
) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
KnightsCard {
Column {
Expand All @@ -80,14 +78,14 @@ private fun LightDarkThemeCard(darkTheme: Boolean = LocalDarkTheme.current) {
selected = !darkTheme,
titleRes = R.string.light_mode,
imageRes = R.drawable.img_light_mode,
onClick = { changeDarkTheme(false) },
onClick = { onChangeDarkTheme(false) },
modifier = cardModifier,
)
ThemeCard(
selected = darkTheme,
titleRes = R.string.dark_mode,
imageRes = R.drawable.img_dark_mode,
onClick = { changeDarkTheme(true) },
onClick = { onChangeDarkTheme(true) },
modifier = cardModifier,
)
}
Expand Down Expand Up @@ -140,6 +138,6 @@ private fun ThemeCard(
@Composable
private fun SettingScreenPreview() {
KnightsTheme {
SettingScreen(PaddingValues(0.dp))
SettingScreen(PaddingValues(0.dp)) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ fun NavController.navigateSetting(navOptions: NavOptions) {

fun NavGraphBuilder.settingNavGraph(
padding: PaddingValues,
onChangeDarkTheme: (Boolean) -> Unit
) {
composable(route = SettingRoute.route) {
SettingScreen(padding)
SettingScreen(padding, onChangeDarkTheme)
}
}

Expand Down
Loading

0 comments on commit 50feaf4

Please sign in to comment.