Skip to content

Commit 5a432b5

Browse files
authored
[MERGE] #239 -> develop
[FEAT/#239] 스플래시, 약관동의 내부 로직 구현
2 parents 7b0b7ed + f56fcb0 commit 5a432b5

9 files changed

Lines changed: 223 additions & 24 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.spoony.spoony.data.datasource.local
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
interface TokenDataSource {
6+
suspend fun getAccessToken(): Flow<String>
7+
suspend fun getRefreshToken(): Flow<String>
8+
suspend fun updateAccessToken(accessToken: String)
9+
suspend fun updateRefreshToken(refreshToken: String)
10+
suspend fun clearTokens()
11+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.spoony.spoony.data.datasourceimpl.local
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.edit
6+
import androidx.datastore.preferences.core.stringPreferencesKey
7+
import com.spoony.spoony.data.datasource.local.TokenDataSource
8+
import javax.inject.Inject
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.map
11+
12+
class TokenDataSourceImpl @Inject constructor(
13+
private val datastore: DataStore<Preferences>
14+
) : TokenDataSource {
15+
override suspend fun getAccessToken(): Flow<String> = datastore.data
16+
.map { preferences -> preferences[ACCESS_TOKEN] ?: "" }
17+
18+
override suspend fun getRefreshToken(): Flow<String> = datastore.data
19+
.map { preferences -> preferences[REFRESH_TOKEN] ?: "" }
20+
21+
override suspend fun updateAccessToken(accessToken: String) {
22+
datastore.edit { preferences ->
23+
preferences[ACCESS_TOKEN] = accessToken
24+
}
25+
}
26+
27+
override suspend fun updateRefreshToken(refreshToken: String) {
28+
datastore.edit { preferences ->
29+
preferences[REFRESH_TOKEN] = refreshToken
30+
}
31+
}
32+
33+
override suspend fun clearTokens() {
34+
datastore.edit { preferences ->
35+
preferences[ACCESS_TOKEN] = ""
36+
preferences[REFRESH_TOKEN] = ""
37+
}
38+
}
39+
40+
companion object {
41+
private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN")
42+
private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN")
43+
}
44+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.spoony.spoony.data.di
2+
3+
import android.content.Context
4+
import androidx.datastore.core.DataStore
5+
import androidx.datastore.preferences.core.Preferences
6+
import androidx.datastore.preferences.preferencesDataStore
7+
import com.spoony.spoony.data.datasource.local.TokenDataSource
8+
import com.spoony.spoony.data.datasourceimpl.local.TokenDataSourceImpl
9+
import com.spoony.spoony.data.repositoryimpl.TokenRepositoryImpl
10+
import com.spoony.spoony.domain.repository.TokenRepository
11+
import dagger.Module
12+
import dagger.Provides
13+
import dagger.hilt.InstallIn
14+
import dagger.hilt.android.qualifiers.ApplicationContext
15+
import dagger.hilt.components.SingletonComponent
16+
import javax.inject.Singleton
17+
18+
private const val TOKEN_PREFERENCES = "token_preferences"
19+
private val Context.tokenDataStore: DataStore<Preferences> by preferencesDataStore(name = TOKEN_PREFERENCES)
20+
21+
@Module
22+
@InstallIn(SingletonComponent::class)
23+
object TokenDataStoreModule {
24+
@Provides
25+
@Singleton
26+
fun provideTokenDataStore(
27+
@ApplicationContext context: Context
28+
): DataStore<Preferences> = context.tokenDataStore
29+
30+
@Provides
31+
@Singleton
32+
fun provideTokenDataSource(
33+
dataStore: DataStore<Preferences>
34+
): TokenDataSource = TokenDataSourceImpl(dataStore)
35+
36+
@Provides
37+
@Singleton
38+
fun provideTokenRepository(
39+
dataSource: TokenDataSource
40+
): TokenRepository = TokenRepositoryImpl(dataSource)
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.spoony.spoony.data.repositoryimpl
2+
3+
import com.spoony.spoony.data.datasource.local.TokenDataSource
4+
import com.spoony.spoony.domain.repository.TokenRepository
5+
import javax.inject.Inject
6+
import kotlinx.coroutines.flow.Flow
7+
8+
class TokenRepositoryImpl @Inject constructor(
9+
private val tokenDataSource: TokenDataSource
10+
) : TokenRepository {
11+
override suspend fun getAccessToken(): Flow<String> = tokenDataSource.getAccessToken()
12+
13+
override suspend fun getRefreshToken(): Flow<String> = tokenDataSource.getRefreshToken()
14+
15+
override suspend fun updateAccessToken(accessToken: String) = tokenDataSource.updateAccessToken(accessToken)
16+
17+
override suspend fun updateRefreshToken(refreshToken: String) = tokenDataSource.updateRefreshToken(refreshToken)
18+
19+
override suspend fun clearTokens() = tokenDataSource.clearTokens()
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.spoony.spoony.domain.repository
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
interface TokenRepository {
6+
suspend fun getAccessToken(): Flow<String>
7+
suspend fun getRefreshToken(): Flow<String>
8+
suspend fun updateAccessToken(accessToken: String)
9+
suspend fun updateRefreshToken(refreshToken: String)
10+
suspend fun clearTokens()
11+
}

app/src/main/java/com/spoony/spoony/presentation/auth/termsofservice/TermsOfServiceScreen.kt

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.spoony.spoony.presentation.auth.termsofservice
22

3+
import android.content.Intent
34
import androidx.compose.foundation.layout.Column
45
import androidx.compose.foundation.layout.PaddingValues
56
import androidx.compose.foundation.layout.Spacer
@@ -9,8 +10,12 @@ import androidx.compose.foundation.layout.height
910
import androidx.compose.foundation.layout.padding
1011
import androidx.compose.runtime.Composable
1112
import androidx.compose.runtime.LaunchedEffect
13+
import androidx.compose.runtime.mutableStateListOf
14+
import androidx.compose.runtime.remember
1215
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.platform.LocalContext
1317
import androidx.compose.ui.unit.dp
18+
import androidx.core.net.toUri
1419
import com.google.accompanist.systemuicontroller.rememberSystemUiController
1520
import com.spoony.spoony.core.designsystem.component.button.SpoonyButton
1621
import com.spoony.spoony.core.designsystem.theme.white
@@ -20,6 +25,35 @@ import com.spoony.spoony.presentation.auth.termsofservice.component.AgreeAllButt
2025
import com.spoony.spoony.presentation.auth.termsofservice.component.AgreeTermsButton
2126
import kotlinx.collections.immutable.persistentListOf
2227

28+
private data class SpoonyTerms(
29+
val title: String,
30+
val link: String?,
31+
val isRequired: Boolean
32+
)
33+
34+
private val termsList = persistentListOf(
35+
SpoonyTerms(
36+
title = "만 14세 이상입니다.",
37+
link = "https://github.com/Hyobeen-Park",
38+
isRequired = true
39+
),
40+
SpoonyTerms(
41+
title = "스푸니 서비스 이용약관",
42+
link = "https://github.com/angryPodo",
43+
isRequired = true
44+
),
45+
SpoonyTerms(
46+
title = "개인정보 처리방침",
47+
link = "https://github.com/Roel4990",
48+
isRequired = true
49+
),
50+
SpoonyTerms(
51+
title = "위치기반 서비스 이용약관",
52+
link = "https://github.com/chattymin",
53+
isRequired = true
54+
)
55+
)
56+
2357
@Composable
2458
fun TermsOfServiceRoute(
2559
paddingValues: PaddingValues,
@@ -44,6 +78,12 @@ private fun TermsOfServiceScreen(
4478
paddingValues: PaddingValues,
4579
navigateToMap: () -> Unit
4680
) {
81+
val context = LocalContext.current
82+
val isChecked = remember { mutableStateListOf(*Array(termsList.size) { false }) }
83+
val isRequiredAgreed = termsList
84+
.filter { it.isRequired }
85+
.all { term -> isChecked[termsList.indexOf(term)] }
86+
4787
Column(
4888
modifier = Modifier
4989
.padding(paddingValues)
@@ -52,28 +92,41 @@ private fun TermsOfServiceScreen(
5292
.fillMaxSize()
5393
) {
5494
AgreeAllButton(
55-
onClick = {}
95+
isChecked = isChecked.all { it },
96+
onClick = {
97+
val newValue = !isChecked.all { it }
98+
isChecked.indices.forEach { index ->
99+
isChecked[index] = newValue
100+
}
101+
}
56102
)
57103

58104
Spacer(
59105
modifier = Modifier
60106
.height(34.dp)
61107
)
62108

63-
persistentListOf(
64-
Pair("만 14세 이상입니다.", false),
65-
Pair("스푸니 서비스 이용약관", true),
66-
Pair("개인정보 처리 방침", true),
67-
Pair("위치기반 서비스 이용약관", true)
68-
).forEach { (title, isUnderlined) ->
69-
AgreeTermsButton(
70-
title = title,
71-
onCheckBoxClick = {},
72-
isRequired = true,
73-
isUnderlined = isUnderlined,
74-
modifier = Modifier
75-
.padding(bottom = 16.dp)
76-
)
109+
termsList.forEachIndexed { index, term ->
110+
with(term) {
111+
AgreeTermsButton(
112+
title = title,
113+
onCheckBoxClick = {
114+
isChecked[index] = !isChecked[index]
115+
},
116+
onTitleClick = {
117+
if (link?.isNotBlank() == true) {
118+
Intent(Intent.ACTION_VIEW, link.toUri()).apply {
119+
context.startActivity(this)
120+
}
121+
}
122+
},
123+
isRequired = isRequired,
124+
isChecked = isChecked[index],
125+
isUnderlined = link?.isNotBlank() == true,
126+
modifier = Modifier
127+
.padding(bottom = 16.dp)
128+
)
129+
}
77130
}
78131

79132
Spacer(
@@ -86,6 +139,7 @@ private fun TermsOfServiceScreen(
86139
size = ButtonSize.Xlarge,
87140
style = ButtonStyle.Primary,
88141
onClick = navigateToMap,
142+
enabled = isRequiredAgreed,
89143
modifier = Modifier
90144
.fillMaxWidth()
91145
)

app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ class MainNavigator(
8787
navController.navigateToSignIn(navOptions = navOptions)
8888
}
8989

90-
fun navigateToTermsOfService(navOptions: NavOptions? = null) {
90+
fun navigateToTermsOfService(
91+
navOptions: NavOptions? = navOptions {
92+
popUpTo(NAVIGATION_ROOT) {
93+
inclusive = true
94+
}
95+
}
96+
) {
9197
navController.navigateToTermsOfService(navOptions = navOptions)
9298
}
9399

app/src/main/java/com/spoony/spoony/presentation/splash/SplashScreen.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,28 @@ import androidx.compose.ui.Modifier
1515
import androidx.compose.ui.graphics.vector.ImageVector
1616
import androidx.compose.ui.res.vectorResource
1717
import androidx.compose.ui.unit.dp
18-
import androidx.lifecycle.compose.LocalLifecycleOwner
19-
import androidx.lifecycle.lifecycleScope
18+
import androidx.hilt.navigation.compose.hiltViewModel
2019
import com.google.accompanist.systemuicontroller.rememberSystemUiController
2120
import com.spoony.spoony.R
2221
import com.spoony.spoony.core.designsystem.theme.SpoonyAndroidTheme
2322
import com.spoony.spoony.core.designsystem.theme.main400
24-
import kotlinx.coroutines.delay
25-
import kotlinx.coroutines.launch
2623

2724
@Composable
2825
fun SplashRoute(
2926
navigateToMap: () -> Unit,
30-
navigateToSignIn: () -> Unit
27+
navigateToSignIn: () -> Unit,
28+
viewModel: SplashViewModel = hiltViewModel()
3129
) {
3230
val systemUiController = rememberSystemUiController()
33-
val lifecycleOwner = LocalLifecycleOwner.current
3431

3532
LaunchedEffect(Unit) {
3633
systemUiController.setNavigationBarColor(
3734
color = main400
3835
)
3936

40-
lifecycleOwner.lifecycleScope.launch {
41-
delay(1000)
37+
if (viewModel.hasAccessToken()) {
38+
navigateToMap()
39+
} else {
4240
navigateToSignIn()
4341
}
4442
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.spoony.spoony.presentation.splash
2+
3+
import androidx.lifecycle.ViewModel
4+
import com.spoony.spoony.domain.repository.TokenRepository
5+
import dagger.hilt.android.lifecycle.HiltViewModel
6+
import javax.inject.Inject
7+
import kotlinx.coroutines.flow.first
8+
9+
@HiltViewModel
10+
class SplashViewModel @Inject constructor(
11+
private val tokenRepository: TokenRepository
12+
) : ViewModel() {
13+
suspend fun hasAccessToken(): Boolean = tokenRepository.getAccessToken().first().isNotBlank()
14+
}

0 commit comments

Comments
 (0)