Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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: 0 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ android {
val googleAdmobAppId: String = properties.getProperty("GOOGLE_ADMOB_APP_ID", "")
val googleAdmobUnitId: String = properties.getProperty("GOOGLE_ADMOB_UNIT_ID", "")
val googleAuthWebClientId: String = properties.getProperty("GOOGLE_AUTH_WEB_CLIENT_ID", "")
val allowedDomains: String = properties.getProperty("allowed.webview.domains", "notion.so,google.com")

buildConfigField("String", "GOOGLE_ADMOB_APP_ID", "\"$googleAdmobAppId\"")
buildConfigField("String", "GOOGLE_ADMOB_UNIT_ID", "\"$googleAdmobUnitId\"")
buildConfigField("String", "KAKAO_API_KEY", "\"$kakaoApiKey\"")
buildConfigField("String", "AMPLITUDE_API_KEY", "\"$amplitudeApiKey\"")
buildConfigField("String", "GOOGLE_AUTH_WEB_CLIENT_ID", "\"$googleAuthWebClientId\"")
buildConfigField("String", "ALLOWED_WEBVIEW_DOMAINS", "\"$allowedDomains\"")
manifestPlaceholders["kakaoRedirectUri"] = "kakao$kakaoApiKey"
manifestPlaceholders["GOOGLE_ADMOB_APP_ID"] = googleAdmobAppId
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import com.sopt.clody.R
import com.sopt.clody.core.security.login.LoginSecurityChecker
import com.sopt.clody.core.security.LoginSecurityChecker
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import javax.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sopt.clody.core.security.login
package com.sopt.clody.core.security

import android.content.Context
import android.content.pm.PackageManager
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sopt.clody.core.security.login
package com.sopt.clody.core.security

import android.content.Context

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sopt.clody.core.security.login
package com.sopt.clody.core.security

import dagger.Binds
import dagger.Module
Expand Down

This file was deleted.

17 changes: 13 additions & 4 deletions app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ class OAuthDataStore @Inject constructor(@ApplicationContext context: Context) {
private val Context.dataStore by preferencesDataStore(name = "oauth_pref")
private val dataStore = context.dataStore

suspend fun saveIdToken(token: String) {
dataStore.edit { it[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] = token }
suspend fun saveIdToken(platform: String, token: String) {
if (platform == OAuthProvider.GOOGLE.platform) {
dataStore.edit { it[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] = token }
} else {
dataStore.edit { it[OAuthDataStoreKeys.KAKAO_ID_TOKEN] = token }
}
}

suspend fun getIdToken(): String? {
return dataStore.data.first()[OAuthDataStoreKeys.GOOGLE_ID_TOKEN]
suspend fun getIdToken(platform: String): String? {
return if (platform == OAuthProvider.GOOGLE.platform) {
dataStore.data.first()[OAuthDataStoreKeys.GOOGLE_ID_TOKEN]
} else {
dataStore.data.first()[OAuthDataStoreKeys.KAKAO_ID_TOKEN]
}
}

suspend fun savePlatform(provider: OAuthProvider) {
Expand All @@ -32,6 +40,7 @@ class OAuthDataStore @Inject constructor(@ApplicationContext context: Context) {
suspend fun clear() {
dataStore.edit {
it.remove(OAuthDataStoreKeys.GOOGLE_ID_TOKEN)
it.remove(OAuthDataStoreKeys.KAKAO_ID_TOKEN)
it.remove(OAuthDataStoreKeys.OAUTH_PLATFORM)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sopt.clody.data.datastore
import androidx.datastore.preferences.core.stringPreferencesKey

object OAuthDataStoreKeys {
val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token")
val OAUTH_PLATFORM = stringPreferencesKey("oauth_platform")
val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token")
val KAKAO_ID_TOKEN = stringPreferencesKey("kakao_id_token")
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sopt.clody.data.datastore

enum class OAuthProvider(val apiValue: String) {
enum class OAuthProvider(val platform: String) {
GOOGLE("google"),
KAKAO("kakao"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query

interface DiaryService {
@POST("api/v1/diary")
suspend fun writeDiary(
@Header("Accept-Language") lang: String,
@Body writeDiaryRequestDto: WriteDiaryRequestDto,
): ApiResponse<WriteDiaryResponseDto>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import com.sopt.clody.data.remote.dto.response.ReplyDiaryResponseDto
import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto

interface DiaryRemoteDataSource {
suspend fun writeDiary(date: String, content: List<String>): ApiResponse<WriteDiaryResponseDto>
suspend fun writeDiary(lang: String, date: String, content: List<String>): ApiResponse<WriteDiaryResponseDto>
suspend fun deleteDailyDiary(year: Int, month: Int, date: Int): ApiResponse<DailyDiariesResponseDto>
suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): ApiResponse<DailyDiariesResponseDto>
suspend fun getDiaryTime(year: Int, month: Int, date: Int): ApiResponse<DiaryTimeResponseDto>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import javax.inject.Inject
class DiaryRemoteDataSourceImpl @Inject constructor(
private val diaryService: DiaryService,
) : DiaryRemoteDataSource {
override suspend fun writeDiary(date: String, content: List<String>): ApiResponse<WriteDiaryResponseDto> =
diaryService.writeDiary(WriteDiaryRequestDto(date, content))
override suspend fun writeDiary(lang: String, date: String, content: List<String>): ApiResponse<WriteDiaryResponseDto> =
diaryService.writeDiary(lang, WriteDiaryRequestDto(date, content))

override suspend fun deleteDailyDiary(year: Int, month: Int, date: Int): ApiResponse<DailyDiariesResponseDto> =
diaryService.deleteDailyDiary(year = year, month = month, date = date)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class WriteDiaryRequestDto(
@SerialName("date") val date: String,
@SerialName("content")val content: List<String>,
@SerialName("content") val content: List<String>,
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.sopt.clody.data.remote.dto.response

import com.sopt.clody.data.datastore.OAuthProvider
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class UserInfoResponseDto(
@SerialName("email") val email: String,
@SerialName("name") val name: String,
@SerialName("platform") val platform: OAuthProvider?,
@SerialName("platform") val platform: String,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider validation for platform string values.

The change from OAuthProvider? enum to String reduces compile-time type safety. Ensure that proper validation is implemented elsewhere in the codebase to prevent invalid platform values from causing runtime issues.

Consider adding validation in the data layer or creating a sealed class/enum for platform types:

+@Serializable
+enum class PlatformType {
+    @SerialName("google") GOOGLE,
+    @SerialName("kakao") KAKAO
+}

 @Serializable
 data class UserInfoResponseDto(
     @SerialName("email") val email: String,
     @SerialName("name") val name: String,
-    @SerialName("platform") val platform: String,
+    @SerialName("platform") val platform: PlatformType,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@SerialName("platform") val platform: String,
// Add this enum (e.g. at the top of the file, after imports)
@Serializable
enum class PlatformType {
@SerialName("google") GOOGLE,
@SerialName("kakao") KAKAO
}
@Serializable
data class UserInfoResponseDto(
@SerialName("email") val email: String,
@SerialName("name") val name: String,
@SerialName("platform") val platform: PlatformType,
)
🤖 Prompt for AI Agents
In
app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt
at line 10, the platform field was changed from an OAuthProvider? enum to a
String, which removes compile-time type safety. To fix this, implement
validation logic either in the data layer or by defining a sealed class or enum
representing valid platform values. This validation should check that the
platform string matches one of the allowed values before it is used, preventing
invalid platform values from causing runtime errors.

)
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import javax.inject.Inject
class DiaryRepositoryImpl @Inject constructor(
private val diaryRemoteDataSource: DiaryRemoteDataSource,
) : DiaryRepository {
override suspend fun writeDiary(date: String, content: List<String>): Result<WriteDiaryResponseDto> =
override suspend fun writeDiary(lang: String, date: String, content: List<String>): Result<WriteDiaryResponseDto> =
runCatching {
diaryRemoteDataSource.writeDiary(date, content).handleApiResponse().getOrThrow()
diaryRemoteDataSource.writeDiary(lang, date, content).handleApiResponse().getOrThrow()
}

override suspend fun deleteDailyDiary(year: Int, month: Int, day: Int): Result<DailyDiariesResponseDto> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto
import com.sopt.clody.domain.model.DraftDiaryContents

interface DiaryRepository {
suspend fun writeDiary(date: String, content: List<String>): Result<WriteDiaryResponseDto>
suspend fun writeDiary(lang: String, date: String, content: List<String>): Result<WriteDiaryResponseDto>
suspend fun deleteDailyDiary(year: Int, month: Int, day: Int): Result<DailyDiariesResponseDto>
suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): Result<DailyDiariesResponseDto>
suspend fun getDiaryTime(year: Int, month: Int, date: Int): Result<DiaryTimeResponseDto>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
Expand All @@ -38,7 +38,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sopt.clody.R
import com.sopt.clody.presentation.ui.component.button.ClodyButton
import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage
import com.sopt.clody.ui.theme.ClodyTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -111,18 +110,16 @@ fun GuideScreen(
AnimatedVisibility(
visible = !isExiting,
exit = fadeOut(animationSpec = tween(1000)), // 1초 페이드 아웃
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
modifier = Modifier.padding(innerPadding),
) {
Column(
modifier = Modifier
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.heightForScreenPercentage(0.21f))
Spacer(modifier = Modifier.weight(1f))
HorizontalPager(
state = pagerState,
modifier = Modifier.weight(4f),
) { page ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
Expand All @@ -134,27 +131,26 @@ fun GuideScreen(
color = ClodyTheme.colors.gray01,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.heightForScreenPercentage(0.02f))
Spacer(modifier = Modifier.height(20.dp))
Text(
text = pages[page].description,
style = ClodyTheme.typography.body1Medium,
color = ClodyTheme.colors.gray05,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.heightForScreenPercentage(0.04f))
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = pages[page].imageRes),
contentDescription = null,
modifier = Modifier
.fillMaxWidth(),
contentScale = ContentScale.Fit,
)
Spacer(modifier = Modifier.weight(1f))
}
}

Spacer(modifier = Modifier.heightForScreenPercentage(0.2f))
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(vertical = 50.dp),
) {
repeat(pagerState.pageCount) { iteration ->
val color = if (pagerState.currentPage == iteration) ClodyTheme.colors.gray03 else ClodyTheme.colors.gray07
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import com.sopt.clody.presentation.ui.auth.signup.page.NickNamePage
import com.sopt.clody.presentation.ui.auth.signup.page.TermsOfServicePage
import com.sopt.clody.presentation.ui.component.dialog.FailureDialog
import com.sopt.clody.presentation.utils.extension.repeatOnStarted
import com.sopt.clody.presentation.utils.openExternalBrowser

@Composable
fun SignUpRoute(
viewModel: SignUpViewModel = mavericksViewModel(),
navigateToHome: () -> Unit,
navigateToPrevious: () -> Unit,
navigateToWebView: (String) -> Unit,
) {
val state by viewModel.collectAsState()
val context = LocalContext.current
Expand All @@ -29,9 +29,7 @@ fun SignUpRoute(
viewModel.sideEffects.collect { effect ->
when (effect) {
is SignUpContract.SignUpSideEffect.NavigateToTimeReminder -> navigateToHome()
is SignUpContract.SignUpSideEffect.NavigateToWebView -> {
navigateToWebView(effect.url) // ✅ WebView 이동 처리
}
is SignUpContract.SignUpSideEffect.NavigateToWebView -> { openExternalBrowser(context, effect.url) }
is SignUpContract.SignUpSideEffect.ShowMessage -> {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,45 +156,43 @@ class SignUpViewModel @AssistedInject constructor(
val fcmToken = fcmTokenProvider.getToken().orEmpty()

if (platform == OAuthProvider.GOOGLE) {
val idToken = oAuthDataStore.getIdToken()
val idToken = oAuthDataStore.getIdToken(platform = "google")
if (idToken.isNullOrBlank()) {
setState { copy(errorMessage = "Google ID Token이 없습니다.", isLoading = false) }
return
}
val request = SignUpRequestDto(
platform = OAuthProvider.GOOGLE.apiValue,
platform = OAuthProvider.GOOGLE.platform,
name = state.nickname,
fcmToken = fcmToken,
)

val result = authRepository.signUp("Bearer $idToken", request)
handleSignUpResult(result, isGoogle = true)
handleSignUpResult(result)
} else {
loginSdk.login(context).fold(
onSuccess = { token ->
val request = SignUpRequestDto(
platform = OAuthProvider.KAKAO.apiValue,
name = state.nickname,
fcmToken = fcmToken,
)
val result = authRepository.signUp("Bearer ${token.value}", request)
handleSignUpResult(result, isGoogle = false)
},
onFailure = {
setState { copy(errorMessage = "로그인에 실패했어요~", isLoading = false) }
},
val idToken = oAuthDataStore.getIdToken(platform = "kakao")
if (idToken.isNullOrBlank()) {
setState { copy(errorMessage = "Kakao ID Token이 없습니다.", isLoading = false) }
return
}
val request = SignUpRequestDto(
platform = OAuthProvider.KAKAO.platform,
name = state.nickname,
fcmToken = fcmToken,
)

val result = authRepository.signUp("Bearer $idToken", request)
handleSignUpResult(result)
}
}

private suspend fun handleSignUpResult(
result: Result<SignUpResponseDto>,
isGoogle: Boolean,
) {
result.fold(
onSuccess = {
tokenRepository.setTokens(it.accessToken, it.refreshToken)
if (isGoogle) oAuthDataStore.clear()
oAuthDataStore.clear()
_sideEffects.send(SignUpContract.SignUpSideEffect.NavigateToTimeReminder)
},
onFailure = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import com.sopt.clody.presentation.utils.navigation.Route
fun NavGraphBuilder.signUpScreen(
navigateToHome: () -> Unit,
navigateToPrevious: () -> Unit,
navigateToWebView: (String) -> Unit,
) {
composable<Route.SignUp> {
SignUpRoute(
navigateToHome = navigateToHome,
navigateToPrevious = navigateToPrevious,
navigateToWebView = navigateToWebView,
)
}
}
Expand Down
Loading