diff --git a/.gitignore b/.gitignore index ed4126c7..ece18a9d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ render.experimental.xml # Keystore files *.jks *.keystore +app/release.keystore +app/debug.keystore # Google Services (e.g. APIs or Firebase) google-services.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 65ee0699..a422900c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,25 @@ plugins { android { namespace = "com.teamsolply.solply" + + signingConfigs { + getByName("debug") { + keyAlias = "androiddebugkey" + keyPassword = "android" + storeFile = File("${project.rootDir.absolutePath}/keystore/debug.keystore") + } + } + + buildTypes { + debug { + signingConfig = signingConfigs.getByName("debug") + } + release { + signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } } dependencies { @@ -32,6 +51,7 @@ dependencies { implementation(projects.local.main) implementation(projects.local.oauth) implementation(projects.local.place) + implementation(projects.remote.oauth) implementation(projects.remote.onboarding) implementation(projects.remote.place) implementation(projects.remote.course) diff --git a/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/textfield/SolplyTextField.kt b/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/textfield/SolplyTextField.kt index e62decc6..60a2442a 100644 --- a/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/textfield/SolplyTextField.kt +++ b/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/textfield/SolplyTextField.kt @@ -114,12 +114,12 @@ private fun BaseTextField( @Composable fun SolplyNicknameTextField( value: String, + isNicknameDuplicate: Boolean, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, placeholder: String = "여기에 입력하세요.", maxLength: Int = 8, minLength: Int = 2, - onDuplicateCheck: (String) -> Boolean, checkNicknameValidate: (String) -> Boolean ) { var validationState by remember { mutableStateOf(NickNameValidateState.Empty) } @@ -130,16 +130,16 @@ fun SolplyNicknameTextField( NickNameValidateState.MaxLength, NickNameValidateState.Typing ) || - (validationState == NickNameValidateState.Empty && value.isNotEmpty()) + (validationState == NickNameValidateState.Empty && value.isNotEmpty()) LaunchedEffect(value) { if (value.isNotEmpty()) { isTyping = true delay(300) - if(value.length + NickNameValidateState.TooShort + ) -> Pair(SolplyTheme.colors.white, SolplyTheme.colors.red600) validationState == NickNameValidateState.Typing -> diff --git a/core/network/src/main/java/com/teamsolply/solply/network/AccessTokenInterceptor.kt b/core/network/src/main/java/com/teamsolply/solply/network/AccessTokenInterceptor.kt index a1e36030..4e13ec52 100644 --- a/core/network/src/main/java/com/teamsolply/solply/network/AccessTokenInterceptor.kt +++ b/core/network/src/main/java/com/teamsolply/solply/network/AccessTokenInterceptor.kt @@ -32,13 +32,12 @@ class AccessTokenInterceptor @Inject constructor( originalRequest } - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Request URL: ${newRequest.url}") - Log.d( - TAG, - "Authorization header: ${if (newRequest.header("Authorization") != null) "Bearer [REDACTED]" else "null"}" - ) - } + Log.d(TAG, "Request URL: ${newRequest.url}") + Log.d( + TAG, + "Authorization header: ${if (newRequest.header("Authorization") != null) "Bearer [REDACTED]" else "null"}" + ) + return chain.proceed(newRequest) } diff --git a/core/network/src/main/java/com/teamsolply/solply/network/service/TokenRefreshService.kt b/core/network/src/main/java/com/teamsolply/solply/network/service/TokenRefreshService.kt index 9d25004e..af3b4c45 100644 --- a/core/network/src/main/java/com/teamsolply/solply/network/service/TokenRefreshService.kt +++ b/core/network/src/main/java/com/teamsolply/solply/network/service/TokenRefreshService.kt @@ -5,8 +5,8 @@ import retrofit2.http.Header import retrofit2.http.POST interface TokenRefreshService { - @POST("") + @POST("api/auth/refresh") suspend fun postRefreshJwtToken( - @Header("Authorization") refreshToken: String + @Header("Refresh-Token") refreshToken: String ): ResponsePostAuthRefreshDto } diff --git a/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/request/SocialLoginRequestDto.kt b/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/request/SocialLoginRequestDto.kt new file mode 100644 index 00000000..109cf2e4 --- /dev/null +++ b/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/request/SocialLoginRequestDto.kt @@ -0,0 +1,10 @@ +package com.teamsolply.solply.oauth.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SocialLoginRequestDto( + @SerialName("oauthAccessToken") + val oauthAccessToken: String +) diff --git a/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/response/SocialLoginResponseDto.kt b/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/response/SocialLoginResponseDto.kt new file mode 100644 index 00000000..5e76141d --- /dev/null +++ b/data/oauth/src/main/java/com/teamsolply/solply/oauth/dto/response/SocialLoginResponseDto.kt @@ -0,0 +1,14 @@ +package com.teamsolply.solply.oauth.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SocialLoginResponseDto( + @SerialName("accessToken") + val accessToken: String, + @SerialName("refreshToken") + val refreshToken: String, + @SerialName("isNewUser") + val isNewUser: Boolean +) diff --git a/data/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepositoryImpl.kt b/data/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepositoryImpl.kt index 900a7170..2c7a3021 100644 --- a/data/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepositoryImpl.kt +++ b/data/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepositoryImpl.kt @@ -2,15 +2,35 @@ package com.teamsolply.solply.oauth.repository import com.teamsolply.solply.oauth.model.TokenEntity import com.teamsolply.solply.oauth.source.OauthLocalDataSource +import com.teamsolply.solply.oauth.source.OauthRemoteDataSource import javax.inject.Inject class OauthRepositoryImpl @Inject constructor( - private val oauthLocalDataSource: OauthLocalDataSource + private val oauthLocalDataSource: OauthLocalDataSource, + private val oauthRemoteDataSource: OauthRemoteDataSource ) : OauthRepository { - override suspend fun saveJwtToken(jwtToken: TokenEntity): Result = runCatching { - oauthLocalDataSource.setAuthLocalData( - accessToken = jwtToken.accessToken, - refreshToken = jwtToken.refreshToken + + override suspend fun postSocialLogin( + provider: String, + oauthAccessToken: String + ): Result = runCatching { + oauthRemoteDataSource.socialLogin( + provider = provider, + oauthAccessToken = oauthAccessToken + ) + }.mapCatching { + TokenEntity( + accessToken = it.accessToken, + refreshToken = it.refreshToken, + isNewUser = it.isNewUser ) } + + override suspend fun saveJwtToken(accessToken: String, refreshToken: String): Result = + runCatching { + oauthLocalDataSource.setAuthLocalData( + accessToken = accessToken, + refreshToken = refreshToken + ) + } } diff --git a/data/oauth/src/main/java/com/teamsolply/solply/oauth/source/OauthRemoteDataSource.kt b/data/oauth/src/main/java/com/teamsolply/solply/oauth/source/OauthRemoteDataSource.kt new file mode 100644 index 00000000..50e28fbe --- /dev/null +++ b/data/oauth/src/main/java/com/teamsolply/solply/oauth/source/OauthRemoteDataSource.kt @@ -0,0 +1,7 @@ +package com.teamsolply.solply.oauth.source + +import com.teamsolply.solply.oauth.dto.response.SocialLoginResponseDto + +interface OauthRemoteDataSource { + suspend fun socialLogin(provider: String, oauthAccessToken: String): SocialLoginResponseDto +} diff --git a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/SignUpRequestDto.kt b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/PatchUserInfoRequestDto.kt similarity index 51% rename from remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/SignUpRequestDto.kt rename to data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/PatchUserInfoRequestDto.kt index fb064e68..8f6e6862 100644 --- a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/SignUpRequestDto.kt +++ b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/request/PatchUserInfoRequestDto.kt @@ -4,9 +4,11 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class SignUpRequestDto( +data class PatchUserInfoRequestDto( + @SerialName("favoriteTown") + val favoriteTown: Long, + @SerialName("persona") + val persona: String, @SerialName("nickname") - val nickname: String, - @SerialName("id") - val id: Int + val nickname: String ) diff --git a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/NicknameDuplicateResponseDto.kt b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/NicknameDuplicateResponseDto.kt new file mode 100644 index 00000000..25bf8926 --- /dev/null +++ b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/NicknameDuplicateResponseDto.kt @@ -0,0 +1,10 @@ +package com.teamsolply.solply.onboarding.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NicknameDuplicateResponseDto( + @SerialName("isDuplicated") + val isDuplicated: Boolean +) diff --git a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/PatchUserInfoResponseDto.kt b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/PatchUserInfoResponseDto.kt new file mode 100644 index 00000000..1d57735f --- /dev/null +++ b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/dto/response/PatchUserInfoResponseDto.kt @@ -0,0 +1,16 @@ +package com.teamsolply.solply.onboarding.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PatchUserInfoResponseDto( + @SerialName("favoriteTownId") + val favoriteTownId: Long, + @SerialName("favoriteTownName") + val favoriteTownName: String, + @SerialName("persona") + val persona: String, + @SerialName("nickname") + val nickname: String +) diff --git a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepositoryImpl.kt b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepositoryImpl.kt index b1737c87..32ad429f 100644 --- a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepositoryImpl.kt +++ b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepositoryImpl.kt @@ -1,16 +1,35 @@ package com.teamsolply.solply.onboarding.repository -import com.teamsolply.solply.onboarding.model.SignUpEntity +import com.teamsolply.solply.onboarding.dto.request.PatchUserInfoRequestDto +import com.teamsolply.solply.onboarding.model.UserInfoEntity import com.teamsolply.solply.onboarding.source.OnBoardingRemoteDataSource import javax.inject.Inject class OnBoardingRepositoryImpl @Inject constructor( private val onBoardingRemoteDataSource: OnBoardingRemoteDataSource ) : OnBoardingRepository { - override suspend fun signUp(signUpInfo: SignUpEntity) = runCatching { - onBoardingRemoteDataSource.signUp( - nickname = signUpInfo.nickname, - id = signUpInfo.userId + override suspend fun checkNicknameDuplicate(nickname: String): Result = runCatching { + onBoardingRemoteDataSource.checkNicknameDuplicate(nickname = nickname).isDuplicated + } + + override suspend fun patchUserInfo( + favoriteTown: Long, + persona: String, + nickname: String + ): Result = runCatching { + onBoardingRemoteDataSource.patchUserInfo( + PatchUserInfoRequestDto( + favoriteTown = favoriteTown, + persona = persona, + nickname = nickname + ) + ) + }.mapCatching { + UserInfoEntity( + favoriteTownId = it.favoriteTownId, + favoriteTownName = it.favoriteTownName, + persona = it.persona, + nickname = it.nickname ) } } diff --git a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/source/OnBoardingRemoteDataSource.kt b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/source/OnBoardingRemoteDataSource.kt index a71a0cfd..7cc21535 100644 --- a/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/source/OnBoardingRemoteDataSource.kt +++ b/data/onboarding/src/main/java/com/teamsolply/solply/onboarding/source/OnBoardingRemoteDataSource.kt @@ -1,5 +1,10 @@ package com.teamsolply.solply.onboarding.source +import com.teamsolply.solply.onboarding.dto.request.PatchUserInfoRequestDto +import com.teamsolply.solply.onboarding.dto.response.NicknameDuplicateResponseDto +import com.teamsolply.solply.onboarding.dto.response.PatchUserInfoResponseDto + interface OnBoardingRemoteDataSource { - suspend fun signUp(nickname: String, id: Int) + suspend fun checkNicknameDuplicate(nickname: String): NicknameDuplicateResponseDto + suspend fun patchUserInfo(patchUserInfoRequestDto: PatchUserInfoRequestDto): PatchUserInfoResponseDto } diff --git a/domain/oauth/src/main/java/com/teamsolply/solply/oauth/model/TokenEntity.kt b/domain/oauth/src/main/java/com/teamsolply/solply/oauth/model/TokenEntity.kt index 67ca7e04..c27c9ef6 100644 --- a/domain/oauth/src/main/java/com/teamsolply/solply/oauth/model/TokenEntity.kt +++ b/domain/oauth/src/main/java/com/teamsolply/solply/oauth/model/TokenEntity.kt @@ -2,5 +2,6 @@ package com.teamsolply.solply.oauth.model data class TokenEntity( val accessToken: String, - val refreshToken: String + val refreshToken: String, + val isNewUser: Boolean ) diff --git a/domain/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepository.kt b/domain/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepository.kt index b21cd4f7..9498e6cd 100644 --- a/domain/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepository.kt +++ b/domain/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepository.kt @@ -3,5 +3,6 @@ package com.teamsolply.solply.oauth.repository import com.teamsolply.solply.oauth.model.TokenEntity interface OauthRepository { - suspend fun saveJwtToken(jwtToken: TokenEntity): Result + suspend fun postSocialLogin(provider: String, oauthAccessToken: String): Result + suspend fun saveJwtToken(accessToken: String, refreshToken: String): Result } diff --git a/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/SignUpEntity.kt b/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/SignUpEntity.kt deleted file mode 100644 index 9f20b8ca..00000000 --- a/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/SignUpEntity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.teamsolply.solply.onboarding.model - -data class SignUpEntity( - val nickname: String, - val userId: Int -) diff --git a/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/UserInfoEntity.kt b/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/UserInfoEntity.kt new file mode 100644 index 00000000..227760d5 --- /dev/null +++ b/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/model/UserInfoEntity.kt @@ -0,0 +1,8 @@ +package com.teamsolply.solply.onboarding.model + +data class UserInfoEntity( + val favoriteTownId: Long?, + val favoriteTownName: String, + val persona: String, + val nickname: String +) diff --git a/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepository.kt b/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepository.kt index ddfd738c..14eee464 100644 --- a/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepository.kt +++ b/domain/onboarding/src/main/java/com/teamsolply/solply/onboarding/repository/OnBoardingRepository.kt @@ -1,7 +1,12 @@ package com.teamsolply.solply.onboarding.repository -import com.teamsolply.solply.onboarding.model.SignUpEntity +import com.teamsolply.solply.onboarding.model.UserInfoEntity interface OnBoardingRepository { - suspend fun signUp(signUpInfo: SignUpEntity): Result + suspend fun checkNicknameDuplicate(nickname: String): Result + suspend fun patchUserInfo( + favoriteTown: Long, + persona: String, + nickname: String + ): Result } diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt index 778dc704..a5542d50 100644 --- a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt @@ -16,7 +16,6 @@ import com.teamsolply.solply.mypage.collection.course.navigateCourseCollection import com.teamsolply.solply.mypage.collection.place.navigatePlaceCollection import com.teamsolply.solply.mypage.navigation.navigateMypage import com.teamsolply.solply.oauth.navigation.navigateOauth -import com.teamsolply.solply.onboarding.navigation.OnBoarding import com.teamsolply.solply.onboarding.navigation.navigateOnBoarding import com.teamsolply.solply.place.navigation.navigatePlace @@ -27,7 +26,7 @@ internal class MainNavigator( @Composable get() = navController .currentBackStackEntryAsState().value?.destination - val startDestination = OnBoarding + val startDestination = Splash val currentTab: MainNavTab? @Composable get() = MainNavTab.find { tab -> diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt index 0c9cf097..9b8ee011 100644 --- a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt +++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt @@ -130,6 +130,15 @@ internal fun MainScreen( launchSingleTop = true } navigator.navigateToOnboarding(navOptions) + }, + navigateToPlace = { + val navOptions = navOptions { + popUpTo(0) { + inclusive = true + } + launchSingleTop = true + } + navigator.navigateToPlace(navOptions = navOptions) } ) onBoardingNavGraph( diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt index eacd739a..5d6bbc03 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt @@ -10,11 +10,17 @@ data class OauthState( sealed interface OauthIntent : UiIntent { data object KakaoLoginClick : OauthIntent - data class KakaoLoginSuccess(val accessToken: String, val refreshToken: String?) : OauthIntent + data class KakaoLoginSuccess(val provider: String, val accessToken: String) : OauthIntent data class KakaoLoginFailure(val error: Throwable) : OauthIntent + data class SaveJwtToken( + val accessToken: String, + val refreshToken: String, + val isNewUser: Boolean + ) : OauthIntent } sealed interface OauthSideEffect : SideEffect { data object StartKakaoLogin : OauthSideEffect data object NavigateToOnBoarding : OauthSideEffect + data object NavigateToPlace : OauthSideEffect } diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt index c26d0a79..c512627d 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt @@ -44,6 +44,7 @@ import kotlinx.coroutines.flow.collectLatest fun OauthRoute( paddingValues: PaddingValues, navigateToOnBoarding: () -> Unit, + navigateToPlace: () -> Unit, viewModel: OauthViewModel = hiltViewModel() ) { val context = LocalContext.current @@ -54,24 +55,18 @@ fun OauthRoute( when (sideEffect) { OauthSideEffect.StartKakaoLogin -> startKakaoLogin( context = context, - onSuccess = { accessToken, refreshToken -> + onSuccess = { accessToken, _ -> viewModel.sendIntent( OauthIntent.KakaoLoginSuccess( - accessToken = accessToken, - refreshToken = refreshToken + provider = "KAKAO", + accessToken = accessToken ) ) - Log.d( - "asdasdasd", - "accessToken: ${accessToken}\n refreshToken: $refreshToken" - ) - }, - onFailure = { error -> - Log.d("asdasdasd", error.toString()) } ) OauthSideEffect.NavigateToOnBoarding -> navigateToOnBoarding() + OauthSideEffect.NavigateToPlace -> navigateToPlace() } } } diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt index dbd02985..a728459f 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt @@ -1,7 +1,7 @@ package com.teamsolply.solply.oauth +import android.util.Log import androidx.lifecycle.viewModelScope -import com.teamsolply.solply.oauth.model.TokenEntity import com.teamsolply.solply.oauth.repository.OauthRepository import com.teamsolply.solply.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -16,28 +16,50 @@ class OauthViewModel @Inject constructor( override fun handleIntent(intent: OauthIntent) { when (intent) { OauthIntent.KakaoLoginClick -> postSideEffect(OauthSideEffect.StartKakaoLogin) - is OauthIntent.KakaoLoginSuccess -> saveJwtToken( - intent.accessToken, - intent.refreshToken + is OauthIntent.KakaoLoginSuccess -> postSocialLogin( + provider = intent.provider, + oauthAccessToken = intent.accessToken ) is OauthIntent.KakaoLoginFailure -> { TODO() } + + is OauthIntent.SaveJwtToken -> { + viewModelScope.launch { + oauthRepository.saveJwtToken( + intent.accessToken, + intent.refreshToken + ).onSuccess { + if (intent.isNewUser) { + postSideEffect(OauthSideEffect.NavigateToOnBoarding) + } else { + postSideEffect(OauthSideEffect.NavigateToPlace) + } + } + } + } } } - private fun saveJwtToken(accessToken: String, refreshToken: String?) { + private fun postSocialLogin( + provider: String, + oauthAccessToken: String + ) { viewModelScope.launch { - refreshToken?.let { checkedRefreshToken -> - val token = TokenEntity( - accessToken = accessToken, - refreshToken = checkedRefreshToken + oauthRepository.postSocialLogin( + provider = provider, + oauthAccessToken = oauthAccessToken + ).onSuccess { + sendIntent( + OauthIntent.SaveJwtToken( + accessToken = it.accessToken, + refreshToken = it.refreshToken, + isNewUser = it.isNewUser + ) ) - oauthRepository.saveJwtToken(token) - .onSuccess { - postSideEffect(OauthSideEffect.NavigateToOnBoarding) - } + }.onFailure { + Log.d("asdasdasd", it.toString()) } } } diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt index 896561aa..d6d7f46b 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt @@ -17,12 +17,14 @@ fun NavController.navigateOauth( fun NavGraphBuilder.oauthNavGraph( paddingValues: PaddingValues, - navigateToOnBoarding: () -> Unit + navigateToOnBoarding: () -> Unit, + navigateToPlace: () -> Unit ) { composable { OauthRoute( paddingValues = paddingValues, - navigateToOnBoarding = navigateToOnBoarding + navigateToOnBoarding = navigateToOnBoarding, + navigateToPlace = navigateToPlace ) } } diff --git a/feature/onboarding/build.gradle.kts b/feature/onboarding/build.gradle.kts index 2c320c31..fd348125 100644 --- a/feature/onboarding/build.gradle.kts +++ b/feature/onboarding/build.gradle.kts @@ -7,5 +7,6 @@ android { } dependencies { + implementation(projects.domain.onboarding) implementation("androidx.compose.foundation:foundation:1.6.1") } diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingContract.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingContract.kt index 5d31ca2b..2219a8a0 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingContract.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingContract.kt @@ -20,7 +20,8 @@ data class OnBoardingState( ), val selectedPersona: Persona? = null, - val userNickname: String? = null, + val userNickname: String = "", + val isNicknameDuplicate: Boolean = false, val showStartingScreen: Boolean = false ) : UiState @@ -31,7 +32,7 @@ sealed interface OnBoardingIntent : UiIntent { data class OnTownSelected(val townId: Long) : OnBoardingIntent data class OnPersonaChanged(val newPage: Int) : OnBoardingIntent data class OnPersonaSelected(val persona: Persona) : OnBoardingIntent - data class Nickname(val nickname: String) : OnBoardingIntent + data class ChangeInputNickname(val nickname: String) : OnBoardingIntent data object ShowStartingScreen : OnBoardingIntent } diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingScreen.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingScreen.kt index 23c0dab5..8ea89310 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingScreen.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingScreen.kt @@ -1,5 +1,6 @@ package com.teamsolply.solply.onboarding +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -8,26 +9,26 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PageSize import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.teamsolply.solply.designsystem.theme.SolplyTheme -import com.teamsolply.solply.ui.lifecycle.LaunchedEffectWithLifecycle -import kotlinx.coroutines.flow.collectLatest -import androidx.compose.foundation.pager.PageSize -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.teamsolply.solply.designsystem.theme.SolplyTheme import com.teamsolply.solply.onboarding.component.BackHeader import com.teamsolply.solply.onboarding.component.ProgressBar import com.teamsolply.solply.onboarding.screen.NamingScreen import com.teamsolply.solply.onboarding.screen.SelectPersonaScreen import com.teamsolply.solply.onboarding.screen.SelectTownScreen import com.teamsolply.solply.onboarding.screen.StartingScreen +import com.teamsolply.solply.ui.lifecycle.LaunchedEffectWithLifecycle +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable @@ -39,10 +40,14 @@ fun OnBoardingRoute( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() + Log.d("asdasdasd", "${state.selectedTownId} ${state.selectedPersona} ${state.userNickname}") + LaunchedEffectWithLifecycle { viewModel.sideEffect.collectLatest { sideEffect -> when (sideEffect) { - is OnBoardingSideEffect.NavigateToPlace -> {navigateToPlace()} + is OnBoardingSideEffect.NavigateToPlace -> { + navigateToPlace() + } } } } @@ -62,7 +67,10 @@ fun OnBoardingRoute( }, onPageChanged = { viewModel.sendIntent(OnBoardingIntent.OnPageChanged(it)) }, onBoardingIntent = { viewModel.sendIntent(it) }, - navController = navController + navController = navController, + changeInputNickname = { inputNickname -> + viewModel.sendIntent(OnBoardingIntent.ChangeInputNickname(inputNickname)) + } ) } } @@ -74,11 +82,13 @@ fun OnBoardingScreen( onPageChanged: (Int) -> Unit, onBoardingIntent: (OnBoardingIntent) -> Unit, modifier: Modifier = Modifier, - navController: NavController + navController: NavController, + changeInputNickname: (String) -> Unit ) { val pagerState = rememberPagerState( initialPage = state.currentPage, - pageCount = { state.totalPageCount }) + pageCount = { state.totalPageCount } + ) val scope = rememberCoroutineScope() @@ -131,7 +141,7 @@ fun OnBoardingScreen( ProgressBar( pageState = pagerState, - totalpageCount = state.totalPageCount, + totalpageCount = state.totalPageCount ) HorizontalPager( @@ -163,6 +173,9 @@ fun OnBoardingScreen( 2 -> NamingScreen( state = state, + inputNickname = state.userNickname, + isNicknameDuplicate = state.isNicknameDuplicate, + changeInputNickname = changeInputNickname, onNextClick = { scope.launch { pagerState.scrollToPage(pagerState.currentPage + 1) @@ -173,4 +186,4 @@ fun OnBoardingScreen( } } } -} \ No newline at end of file +} diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingViewModel.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingViewModel.kt index 99dd489f..29a80018 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/OnBoardingViewModel.kt @@ -1,11 +1,16 @@ package com.teamsolply.solply.onboarding +import androidx.lifecycle.viewModelScope +import com.teamsolply.solply.onboarding.repository.OnBoardingRepository import com.teamsolply.solply.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class OnBoardingViewModel @Inject constructor() : +class OnBoardingViewModel @Inject constructor( + private val onBoardingRepository: OnBoardingRepository +) : BaseViewModel( OnBoardingState() ) { @@ -36,9 +41,23 @@ class OnBoardingViewModel @Inject constructor() : is OnBoardingIntent.ShowStartingScreen -> { reduce { copy(showStartingScreen = true) } } - is OnBoardingIntent.Nickname -> { + + is OnBoardingIntent.ChangeInputNickname -> { + viewModelScope.launch { + onBoardingRepository.checkNicknameDuplicate(nickname = intent.nickname) + .onSuccess { + reduce { copy(isNicknameDuplicate = it) } + } + } reduce { copy(userNickname = intent.nickname) } } } } + + private fun patchUserInfo() { + viewModelScope.launch { + // TODO. 여기서 회원가입 api 쏘기 + // onBoardingRepository + } + } } diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/component/BackHeader.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/component/BackHeader.kt index ff61bb70..f3e23276 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/component/BackHeader.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/component/BackHeader.kt @@ -3,7 +3,6 @@ package com.teamsolply.solply.onboarding.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -14,7 +13,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.teamsolply.solply.ui.extension.customClickable - @Composable fun BackHeader( barText: String, diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/navigation/OnBoardingNavigation.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/navigation/OnBoardingNavigation.kt index 3497e36f..0d863e7b 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/navigation/OnBoardingNavigation.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/navigation/OnBoardingNavigation.kt @@ -18,9 +18,9 @@ fun NavController.navigateOnBoarding( fun NavGraphBuilder.onBoardingNavGraph( navController: NavController, paddingValues: PaddingValues, - navigateToPlace: () -> Unit, + navigateToPlace: () -> Unit - ) { +) { composable { OnBoardingRoute( paddingValues = paddingValues, diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/NamingScreen.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/NamingScreen.kt index 60f046a6..39c20609 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/NamingScreen.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/NamingScreen.kt @@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager @@ -23,12 +21,12 @@ import com.teamsolply.solply.ui.extension.addFocusCleaner @Composable fun NamingScreen( state: OnBoardingState, + inputNickname: String, + isNicknameDuplicate: Boolean, + changeInputNickname: (String) -> Unit, onNextClick: () -> Unit, onBoardingIntent: (OnBoardingIntent) -> Unit ) { - var nickname by remember { mutableStateOf("") } - - val isButtonEnabled = nickname.isNotBlank() val focusManager = LocalFocusManager.current Column( @@ -49,12 +47,9 @@ fun NamingScreen( ) SolplyNicknameTextField( - value = nickname, - onValueChange = { nickname = it }, - onDuplicateCheck = { input -> - println("중복 체크 요청: $input") - input.lowercase() == "solply" - }, + value = inputNickname, + isNicknameDuplicate = state.isNicknameDuplicate, + onValueChange = { changeInputNickname(it) }, checkNicknameValidate = { input -> input.all { it.isLetterOrDigit() } } @@ -66,14 +61,13 @@ fun NamingScreen( modifier = Modifier .padding(bottom = 24.dp), onClick = { - if (isButtonEnabled) { - onBoardingIntent(OnBoardingIntent.Nickname(nickname)) + if (state.userNickname.isNotBlank()) { onBoardingIntent(OnBoardingIntent.ShowStartingScreen) } }, - selected = isButtonEnabled, + selected = state.userNickname.isNotBlank(), textStyle = SolplyTheme.typography.button16M, - textColor = if (isButtonEnabled) SolplyTheme.colors.white else SolplyTheme.colors.gray800, + textColor = if (state.userNickname.isNotBlank()) SolplyTheme.colors.white else SolplyTheme.colors.gray800, enabledBackgroundColor = SolplyTheme.colors.gray900, disabledBackgroundColor = SolplyTheme.colors.gray300 ) diff --git a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/SelectPersonaScreen.kt b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/SelectPersonaScreen.kt index 18d8fcce..3ce909ae 100644 --- a/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/SelectPersonaScreen.kt +++ b/feature/onboarding/src/main/java/com/teamsolply/solply/onboarding/screen/SelectPersonaScreen.kt @@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp diff --git a/remote/oauth/.gitignore b/remote/oauth/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/remote/oauth/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/remote/oauth/build.gradle.kts b/remote/oauth/build.gradle.kts new file mode 100644 index 00000000..a3d9266e --- /dev/null +++ b/remote/oauth/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.solply.remote) +} + +android { + namespace = "com.teamsolply.solply.oauth" +} + +dependencies { + implementation(projects.core.network) + implementation(projects.core.model) + implementation(projects.data.oauth) +} diff --git a/remote/oauth/src/main/java/com/teamsolply/solply/oauth/datasource/OauthRemoteDataSourceImpl.kt b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/datasource/OauthRemoteDataSourceImpl.kt new file mode 100644 index 00000000..80ec8f5d --- /dev/null +++ b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/datasource/OauthRemoteDataSourceImpl.kt @@ -0,0 +1,19 @@ +package com.teamsolply.solply.oauth.datasource + +import com.teamsolply.solply.oauth.dto.request.SocialLoginRequestDto +import com.teamsolply.solply.oauth.dto.response.SocialLoginResponseDto +import com.teamsolply.solply.oauth.service.OauthService +import com.teamsolply.solply.oauth.source.OauthRemoteDataSource +import javax.inject.Inject + +class OauthRemoteDataSourceImpl @Inject constructor( + private val oauthService: OauthService +) : OauthRemoteDataSource { + override suspend fun socialLogin( + provider: String, + oauthAccessToken: String + ): SocialLoginResponseDto = oauthService.postSocialLogin( + soicialPlatform = provider, + oauthAccessToken = SocialLoginRequestDto(oauthAccessToken = oauthAccessToken) + ).data +} diff --git a/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthRemoteDataModule.kt b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthRemoteDataModule.kt new file mode 100644 index 00000000..1214c94a --- /dev/null +++ b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthRemoteDataModule.kt @@ -0,0 +1,17 @@ +package com.teamsolply.solply.oauth.di + +import com.teamsolply.solply.oauth.datasource.OauthRemoteDataSourceImpl +import com.teamsolply.solply.oauth.source.OauthRemoteDataSource +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class OauthRemoteDataModule { + @Binds + @Singleton + abstract fun bindsOauthRemoteDataSource(oauthRemoteDataSource: OauthRemoteDataSourceImpl): OauthRemoteDataSource +} diff --git a/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthServiceModule.kt b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthServiceModule.kt new file mode 100644 index 00000000..a7235153 --- /dev/null +++ b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/di/OauthServiceModule.kt @@ -0,0 +1,19 @@ +package com.teamsolply.solply.oauth.di + +import com.teamsolply.solply.network.di.NoneAccessToken +import com.teamsolply.solply.oauth.service.OauthService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object OauthServiceModule { + @Provides + @Singleton + fun providesOauthService(@NoneAccessToken retrofit: Retrofit): OauthService = + retrofit.create(OauthService::class.java) +} diff --git a/remote/oauth/src/main/java/com/teamsolply/solply/oauth/service/OauthService.kt b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/service/OauthService.kt new file mode 100644 index 00000000..96df1604 --- /dev/null +++ b/remote/oauth/src/main/java/com/teamsolply/solply/oauth/service/OauthService.kt @@ -0,0 +1,16 @@ +package com.teamsolply.solply.oauth.service + +import com.teamsolply.solply.network.model.BaseResponse +import com.teamsolply.solply.oauth.dto.request.SocialLoginRequestDto +import com.teamsolply.solply.oauth.dto.response.SocialLoginResponseDto +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +interface OauthService { + @POST("api/auth/social/{soicialPlatform}/login") + suspend fun postSocialLogin( + @Path("soicialPlatform") soicialPlatform: String, + @Body oauthAccessToken: SocialLoginRequestDto + ): BaseResponse +} diff --git a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/datasource/OnBoardingRemoteDataSourceImpl.kt b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/datasource/OnBoardingRemoteDataSourceImpl.kt index 968859b1..a1af72fc 100644 --- a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/datasource/OnBoardingRemoteDataSourceImpl.kt +++ b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/datasource/OnBoardingRemoteDataSourceImpl.kt @@ -1,6 +1,8 @@ package com.teamsolply.solply.onboarding.datasource -import com.teamsolply.solply.onboarding.dto.request.SignUpRequestDto +import com.teamsolply.solply.onboarding.dto.request.PatchUserInfoRequestDto +import com.teamsolply.solply.onboarding.dto.response.NicknameDuplicateResponseDto +import com.teamsolply.solply.onboarding.dto.response.PatchUserInfoResponseDto import com.teamsolply.solply.onboarding.service.OnBoardingService import com.teamsolply.solply.onboarding.source.OnBoardingRemoteDataSource import javax.inject.Inject @@ -8,10 +10,9 @@ import javax.inject.Inject class OnBoardingRemoteDataSourceImpl @Inject constructor( private val onBoardingService: OnBoardingService ) : OnBoardingRemoteDataSource { - override suspend fun signUp(nickname: String, id: Int) = onBoardingService.signUp( - SignUpRequestDto( - nickname = nickname, - id = id - ) - ) + override suspend fun checkNicknameDuplicate(nickname: String): NicknameDuplicateResponseDto = + onBoardingService.checkNicknameDuplicate(nickname = nickname).data + + override suspend fun patchUserInfo(patchUserInfoRequestDto: PatchUserInfoRequestDto): PatchUserInfoResponseDto = + onBoardingService.patchUserInfo(patchUserInfoRequestDto = patchUserInfoRequestDto).data } diff --git a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt index e4499004..59b4697f 100644 --- a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt +++ b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt @@ -1,10 +1,23 @@ package com.teamsolply.solply.onboarding.service -import com.teamsolply.solply.onboarding.dto.request.SignUpRequestDto +import com.teamsolply.solply.network.model.BaseResponse +import com.teamsolply.solply.onboarding.dto.request.PatchUserInfoRequestDto +import com.teamsolply.solply.onboarding.dto.response.NicknameDuplicateResponseDto +import com.teamsolply.solply.onboarding.dto.response.PatchUserInfoResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.Query interface OnBoardingService { - suspend fun signUp( - signUpRequestDto: SignUpRequestDto - ) + @GET("api/users/check-nickname") + suspend fun checkNicknameDuplicate( + @Query("nickname") nickname: String + ): BaseResponse + + @PATCH("api/users") + suspend fun patchUserInfo( + @Body patchUserInfoRequestDto: PatchUserInfoRequestDto + ): BaseResponse } diff --git a/settings.gradle.kts b/settings.gradle.kts index a327c010..e0c19692 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -50,6 +50,7 @@ include(":data:maps") include(":local:main") include(":local:oauth") include(":local:place") +include(":remote:oauth") include(":remote:onboarding") include(":remote:place") include(":remote:course")