From d7cf05329da3f44b0e38e9c59dc403e4d9288c1e Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Wed, 20 Dec 2023 14:31:31 +0900 Subject: [PATCH 01/60] =?UTF-8?q?chore:=20Timber=20Tree=20=EC=8B=AC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 ++++ app/src/main/AndroidManifest.xml | 3 ++- app/src/main/java/com/oksusu/susu/CustomTimberTree.kt | 9 +++++++++ app/src/main/java/com/oksusu/susu/SUSUApplication.kt | 11 ++++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/oksusu/susu/CustomTimberTree.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5b5d55df..82b3832d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,6 +14,10 @@ android { versionCode = 1 versionName = "1.0" } + + buildFeatures { + buildConfig = true + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eff5b252..9262901c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools" + package="com.oksusu.susu"> Date: Wed, 20 Dec 2023 20:53:44 +0900 Subject: [PATCH 02/60] =?UTF-8?q?feat:=20kakao=20user=20sdk=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.gitignore | 3 ++- app/build.gradle.kts | 3 +-- .../main/java/com/oksusu/susu/SUSUApplication.kt | 3 +++ feature/loginsignup/.gitignore | 3 ++- feature/loginsignup/build.gradle.kts | 4 ++++ feature/loginsignup/src/main/AndroidManifest.xml | 13 +++++++++++++ gradle/libs.versions.toml | 3 +++ settings.gradle.kts | 1 + 8 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/.gitignore b/app/.gitignore index 42afabfd..e8866d24 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +/src/main/res/values/app_key.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 82b3832d..945f42c6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,8 +2,6 @@ plugins { alias(libs.plugins.susu.android.application) alias(libs.plugins.susu.android.hilt) - alias(libs.plugins.google.services) - alias(libs.plugins.firebase.crashlytics) } android { @@ -22,6 +20,7 @@ android { dependencies { implementation(projects.feature.navigator) + implementation(libs.kakao.sdk.user) implementation(platform(libs.firebase.bom)) implementation(libs.firebase.crashlytics) diff --git a/app/src/main/java/com/oksusu/susu/SUSUApplication.kt b/app/src/main/java/com/oksusu/susu/SUSUApplication.kt index 8785ac66..bc8ce439 100644 --- a/app/src/main/java/com/oksusu/susu/SUSUApplication.kt +++ b/app/src/main/java/com/oksusu/susu/SUSUApplication.kt @@ -1,6 +1,7 @@ package com.oksusu.susu import android.app.Application +import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.HiltAndroidApp import timber.log.Timber @@ -12,5 +13,7 @@ class SUSUApplication : Application() { if (BuildConfig.DEBUG) { Timber.plant(CustomTimberTree()) } + + KakaoSdk.init(this, getString(R.string.kakao_app_key)) } } diff --git a/feature/loginsignup/.gitignore b/feature/loginsignup/.gitignore index 42afabfd..e8866d24 100644 --- a/feature/loginsignup/.gitignore +++ b/feature/loginsignup/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +/src/main/res/values/app_key.xml diff --git a/feature/loginsignup/build.gradle.kts b/feature/loginsignup/build.gradle.kts index 17589670..0e8928cd 100644 --- a/feature/loginsignup/build.gradle.kts +++ b/feature/loginsignup/build.gradle.kts @@ -6,3 +6,7 @@ plugins { android { namespace = "com.susu.feature.loginsignup" } + +dependencies { + implementation(libs.kakao.sdk.user) +} diff --git a/feature/loginsignup/src/main/AndroidManifest.xml b/feature/loginsignup/src/main/AndroidManifest.xml index 8bdb7e14..ffdfe8f3 100644 --- a/feature/loginsignup/src/main/AndroidManifest.xml +++ b/feature/loginsignup/src/main/AndroidManifest.xml @@ -1,4 +1,17 @@ + + + + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d649aafb..85dac190 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,6 +62,8 @@ protobuf-plugin = "0.9.4" protobuf = "3.24.4" junit-junit = "4.13.2" +kakao-user = "2.18.0" + [plugins] ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } @@ -178,6 +180,7 @@ protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin- protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } junit-junit = { group = "junit", name = "junit", version.ref = "junit-junit" } +kakao-sdk-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-user"} [bundles] firebase = ["firebase-analytics"] diff --git a/settings.gradle.kts b/settings.gradle.kts index 8ea19c45..3cccd4a3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ dependencyResolutionManagement { google() mavenCentral() maven(url = "https://jitpack.io") + maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/") } } From c7fababd9b9f3b7b03d18565eed7fd18ae4ef2ef Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Wed, 20 Dec 2023 22:23:50 +0900 Subject: [PATCH 03/60] =?UTF-8?q?feat:=20SplashContract=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20=ED=9B=84=20SplashViewModel=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수수 토큰 만료 여부 체크해야 함 --- .../loginsignup/splash/SplashContract.kt | 18 ++++++++ .../loginsignup/splash/SplashViewModel.kt | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt new file mode 100644 index 00000000..ef7a284a --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt @@ -0,0 +1,18 @@ +package com.susu.feature.loginsignup.splash + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +class SplashContract { + sealed class SplashEffect : SideEffect { + data object NavigateToSignUpVote : SplashEffect() + data object NavigateToLogIn : SplashEffect() + data object NavigateToReceived : SplashEffect() + data object NavigateToSignUp : SplashEffect() + } + + data class SplashState( + val isLoading: Boolean = false, + val selectedIndex: Int = 0, // 온보딩 투표 선택한 항목 + ) : UiState +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt new file mode 100644 index 00000000..6a21179d --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt @@ -0,0 +1,44 @@ +package com.susu.feature.loginsignup.splash + +import androidx.lifecycle.viewModelScope +import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.repository.TokenRepository +import com.susu.feature.loginsignup.KakaoLoginHelper +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SplashViewModel @Inject constructor( + private val kakaoLoginHelper: KakaoLoginHelper, + private val tokenRepository: TokenRepository, +) : BaseViewModel(SplashContract.SplashState()) { + + init { + viewModelScope.launch { + // 1. 과거 카톡 로그인 이력 확인 + if (!kakaoLoginHelper.hasKakaoLoginHistory()) { + // 1-1. 신규 유저 + postSideEffect(SplashContract.SplashEffect.NavigateToSignUpVote) + } else { + // 2. 수수 토큰 여부 확인 + if (tokenRepository.getAccessToken().firstOrNull() == null && + tokenRepository.getRefreshToken().firstOrNull() == null + ) { + // 2-1. 카카오 로그인 후 이탈한 유저 + postSideEffect(SplashContract.SplashEffect.NavigateToSignUp) + } else { + // 3. 수수 토큰 만료 여부 확인 + if (TODO("수수 api 인증")) { + // 3-1. 수수 토큰 유효함. 자동로그인 + postSideEffect(SplashContract.SplashEffect.NavigateToReceived) + } else { + // 3-2. 수수 토큰 만료됨. + postSideEffect(SplashContract.SplashEffect.NavigateToLogIn) + } + } + } + } + } +} From 136f96f2d2dd16f04da7746adaa18b165e651fb0 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 21 Dec 2023 14:17:20 +0900 Subject: [PATCH 04/60] =?UTF-8?q?feat:=20kakao=20login=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/loginsignup/KakaoLoginHelper.kt | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt new file mode 100644 index 00000000..c99ef73e --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt @@ -0,0 +1,92 @@ +package com.susu.feature.loginsignup + +import android.content.Context +import com.kakao.sdk.auth.AuthApiClient +import com.kakao.sdk.common.model.AuthError +import com.kakao.sdk.common.model.ClientError +import com.kakao.sdk.common.model.ClientErrorCause +import com.kakao.sdk.user.UserApiClient +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Singleton + +@Singleton +class KakaoLoginHelper( + @ApplicationContext private val context: Context, +) { + + fun hasKakaoLoginHistory(): Boolean { + return AuthApiClient.instance.hasToken() + } + + fun login( + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) { + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + loginWithKakaoTalk( + context = context, + onSuccess = onSuccess, + onFailed = { error -> + // 의도적인 사용자의 로그인 취소를 제외한 경우에는 카카오계정 로그인으로 재시도 + if (error is AuthError || (error is ClientError && error.reason != ClientErrorCause.Cancelled)) { + loginWithKakaoAccount( + context = context, + onSuccess = onSuccess, + onFailed = onFailed, + ) + } + }, + ) + } else { // 카카오톡 미설치 시 카카오계정 로그인 시도 + loginWithKakaoAccount( + context = context, + onSuccess = onSuccess, + onFailed = onFailed, + ) + } + } + + private fun loginWithKakaoTalk( + context: Context, + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) { + UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> + if (token != null) { + onSuccess(token.accessToken) + } else { + onFailed(error) + } + } + } + + private fun loginWithKakaoAccount( + context: Context, + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) { + UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> + if (token != null) { + onSuccess(token.accessToken) + } else { + onFailed(error) + } + } + } + + fun logout() = runCatching { + UserApiClient.instance.logout { error -> + if (error != null) { + throw error + } + } + } + + fun unlink() = runCatching { + UserApiClient.instance.unlink { error -> + if (error != null) { + throw error + } + } + } +} From 257002393049e25a35bc113dc6c2f2feeb4f7da9 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 21 Dec 2023 23:43:53 +0900 Subject: [PATCH 05/60] =?UTF-8?q?feat:=20=EC=88=98=EC=88=98=20auth=20api?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/core/model/Token.kt | 8 +++ .../src/main/java/com/susu/core/model/User.kt | 7 +++ .../java/com/susu/data/di/NetworkModule.kt | 9 +++- .../java/com/susu/data/model/TokenEntity.kt | 26 ++++++++++ .../java/com/susu/data/model/UserEntity.kt | 23 ++++++++ .../data/model/request/AccessTokenRequest.kt | 8 +++ .../model/response/ValidRegisterResponse.kt | 8 +++ .../java/com/susu/data/network/AuthService.kt | 39 ++++++++++++++ .../susu/data/repository/AuthRepository.kt | 52 +++++++++++++++++++ .../susu/domain/repository/AuthRepository.kt | 12 +++++ 10 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 core/model/src/main/java/com/susu/core/model/Token.kt create mode 100644 core/model/src/main/java/com/susu/core/model/User.kt create mode 100644 data/src/main/java/com/susu/data/model/TokenEntity.kt create mode 100644 data/src/main/java/com/susu/data/model/UserEntity.kt create mode 100644 data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt create mode 100644 data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt create mode 100644 data/src/main/java/com/susu/data/network/AuthService.kt create mode 100644 data/src/main/java/com/susu/data/repository/AuthRepository.kt create mode 100644 domain/src/main/java/com/susu/domain/repository/AuthRepository.kt diff --git a/core/model/src/main/java/com/susu/core/model/Token.kt b/core/model/src/main/java/com/susu/core/model/Token.kt new file mode 100644 index 00000000..59b90874 --- /dev/null +++ b/core/model/src/main/java/com/susu/core/model/Token.kt @@ -0,0 +1,8 @@ +package com.susu.core.model + +data class Token( + val accessToken: String, + val accessTokenExp: String, + val refreshToken: String, + val refreshTokenExp: String, +) diff --git a/core/model/src/main/java/com/susu/core/model/User.kt b/core/model/src/main/java/com/susu/core/model/User.kt new file mode 100644 index 00000000..395e82a9 --- /dev/null +++ b/core/model/src/main/java/com/susu/core/model/User.kt @@ -0,0 +1,7 @@ +package com.susu.core.model + +data class User( + val name: String, + val age: Int, + val birth: Int, +) diff --git a/data/src/main/java/com/susu/data/di/NetworkModule.kt b/data/src/main/java/com/susu/data/di/NetworkModule.kt index f63299eb..30758c99 100644 --- a/data/src/main/java/com/susu/data/di/NetworkModule.kt +++ b/data/src/main/java/com/susu/data/di/NetworkModule.kt @@ -4,6 +4,7 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.susu.data.Constants.RETROFIT_TAG import com.susu.data.extension.isJsonArray import com.susu.data.extension.isJsonObject +import com.susu.data.network.AuthService import com.susu.data.network.TokenAuthenticator import com.susu.data.network.TokenInterceptor import dagger.Module @@ -23,7 +24,7 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object NetworkModule { - private const val BASE_URL = "" + private const val BASE_URL = "https://api.oksusu.site/api/v1/" @Singleton @Provides @@ -74,4 +75,10 @@ object NetworkModule { ignoreUnknownKeys = true } } + + @Singleton + @Provides + fun provideAuthService(retrofit: Retrofit): AuthService { + return retrofit.create(AuthService::class.java) + } } diff --git a/data/src/main/java/com/susu/data/model/TokenEntity.kt b/data/src/main/java/com/susu/data/model/TokenEntity.kt new file mode 100644 index 00000000..3e8379ef --- /dev/null +++ b/data/src/main/java/com/susu/data/model/TokenEntity.kt @@ -0,0 +1,26 @@ +package com.susu.data.model + +import com.susu.core.model.Token +import kotlinx.serialization.Serializable + +@Serializable +data class TokenEntity( + val accessToken: String, + val accessTokenExp: String, + val refreshToken: String, + val refreshTokenExp: String, +) + +fun TokenEntity.toDomain() = Token( + accessToken = accessToken, + accessTokenExp = accessTokenExp, + refreshToken = refreshToken, + refreshTokenExp = refreshTokenExp, +) + +fun Token.toData() = TokenEntity( + accessToken = accessToken, + accessTokenExp = accessTokenExp, + refreshToken = refreshToken, + refreshTokenExp = refreshTokenExp, +) diff --git a/data/src/main/java/com/susu/data/model/UserEntity.kt b/data/src/main/java/com/susu/data/model/UserEntity.kt new file mode 100644 index 00000000..e4988673 --- /dev/null +++ b/data/src/main/java/com/susu/data/model/UserEntity.kt @@ -0,0 +1,23 @@ +package com.susu.data.model + +import com.susu.core.model.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserEntity( + val name: String, + val age: Int, + val birth: Int, +) + +fun UserEntity.toDomain() = User( + name = name, + age = age, + birth = birth, +) + +fun User.toData() = UserEntity( + name = name, + age = age, + birth = birth, +) diff --git a/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt b/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt new file mode 100644 index 00000000..491f0381 --- /dev/null +++ b/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt @@ -0,0 +1,8 @@ +package com.susu.data.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class AccessTokenRequest( + val accessToken: String +) diff --git a/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt b/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt new file mode 100644 index 00000000..7f95d904 --- /dev/null +++ b/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt @@ -0,0 +1,8 @@ +package com.susu.data.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class ValidRegisterResponse( + val canRegister: Boolean +) diff --git a/data/src/main/java/com/susu/data/network/AuthService.kt b/data/src/main/java/com/susu/data/network/AuthService.kt new file mode 100644 index 00000000..86a306df --- /dev/null +++ b/data/src/main/java/com/susu/data/network/AuthService.kt @@ -0,0 +1,39 @@ +package com.susu.data.network + +import com.susu.data.model.TokenEntity +import com.susu.data.model.UserEntity +import com.susu.data.model.request.AccessTokenRequest +import com.susu.data.model.request.RefreshTokenRequest +import com.susu.data.model.response.ValidRegisterResponse +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface AuthService { + @POST("oauth/{provider}/login") + suspend fun login( + @Path("provider") provider: String, + @Body accessTokenRequest: AccessTokenRequest, + ): TokenEntity + + @POST("oauth/{provider}/sign-up") + suspend fun signUp( + @Path("provider") provider: String, + @Body user: UserEntity, + ): TokenEntity + + @GET("oauth/{provider}/sign-up/valid") + suspend fun checkValidRegister( + @Path("provider") provider: String, + @Body accessTokenRequest: AccessTokenRequest, + ): ValidRegisterResponse + + @POST("auth/logout") + suspend fun logout() + + @POST("auth/token/refresh") + suspend fun refreshAccessToken( + @Body refreshTokenRequest: RefreshTokenRequest, + ): TokenEntity +} diff --git a/data/src/main/java/com/susu/data/repository/AuthRepository.kt b/data/src/main/java/com/susu/data/repository/AuthRepository.kt new file mode 100644 index 00000000..79038aff --- /dev/null +++ b/data/src/main/java/com/susu/data/repository/AuthRepository.kt @@ -0,0 +1,52 @@ +package com.susu.data.repository + +import com.susu.core.model.User +import com.susu.data.model.SnsProviders +import com.susu.data.model.request.AccessTokenRequest +import com.susu.data.model.request.RefreshTokenRequest +import com.susu.data.model.toData +import com.susu.data.model.toDomain +import com.susu.data.network.AuthService +import com.susu.domain.repository.AuthRepository +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val authService: AuthService, +) : AuthRepository { + override suspend fun login( + snsAccessToken: String, + ) = kotlin.runCatching { + authService.login( + provider = SnsProviders.Kakao.name, + accessTokenRequest = AccessTokenRequest(snsAccessToken), + ).toDomain() + } + + override suspend fun signUp( + user: User, + ) = runCatching { + authService.signUp( + provider = SnsProviders.Kakao.name, + user = user.toData(), + ).toDomain() + } + + override suspend fun canRegister( + snsAccessToken: String, + ): Boolean { + return authService.checkValidRegister( + provider = SnsProviders.Kakao.name, + accessTokenRequest = AccessTokenRequest(snsAccessToken), + ).canRegister + } + + override suspend fun logout() { + authService.logout() + } + + override suspend fun refreshAccessToken(refreshToken: String) = runCatching { + authService.refreshAccessToken( + refreshTokenRequest = RefreshTokenRequest(refreshToken) + ).toDomain() + } +} diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt new file mode 100644 index 00000000..e1f914d9 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt @@ -0,0 +1,12 @@ +package com.susu.domain.repository + +import com.susu.core.model.Token +import com.susu.core.model.User + +interface AuthRepository { + suspend fun login(snsAccessToken: String): Result + suspend fun signUp(user: User): Result + suspend fun canRegister(snsAccessToken: String): Boolean + suspend fun logout() + suspend fun refreshAccessToken(refreshToken: String): Result +} From 70010662438a93147639b41cb5bea39a3a5a5ae7 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 21 Dec 2023 23:46:27 +0900 Subject: [PATCH 06/60] =?UTF-8?q?refactor:=20domain=EC=97=90=20KakaoLoginP?= =?UTF-8?q?rovider=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mypage module에서도 로그아웃을 위해 접근 가능해야 함 --- .../susu/domain/util/KakaoLoginProvider.kt | 23 +++++++++++++++++++ ...aoLoginHelper.kt => KakaoLoginProvider.kt} | 23 ++++++++----------- 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt rename feature/loginsignup/src/main/java/com/susu/feature/loginsignup/{KakaoLoginHelper.kt => KakaoLoginProvider.kt} (85%) diff --git a/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt new file mode 100644 index 00000000..24847bfb --- /dev/null +++ b/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt @@ -0,0 +1,23 @@ +package com.susu.domain.util + +interface KakaoLoginProvider { + fun hasKakaoLoginHistory(): Boolean + + fun login( + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) + + fun loginWithKakaoTalk( + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) + + fun loginWithKakaoAccount( + onSuccess: (String) -> Unit, + onFailed: (Throwable?) -> Unit, + ) + + fun logout(): Result + fun unlink(): Result +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProvider.kt similarity index 85% rename from feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt rename to feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProvider.kt index c99ef73e..0242d367 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProvider.kt @@ -6,31 +6,31 @@ import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient +import com.susu.domain.util.KakaoLoginProvider import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import javax.inject.Singleton @Singleton -class KakaoLoginHelper( +class KakaoLoginProviderImpl @Inject constructor( @ApplicationContext private val context: Context, -) { +) : KakaoLoginProvider { - fun hasKakaoLoginHistory(): Boolean { + override fun hasKakaoLoginHistory(): Boolean { return AuthApiClient.instance.hasToken() } - fun login( + override fun login( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { loginWithKakaoTalk( - context = context, onSuccess = onSuccess, onFailed = { error -> // 의도적인 사용자의 로그인 취소를 제외한 경우에는 카카오계정 로그인으로 재시도 if (error is AuthError || (error is ClientError && error.reason != ClientErrorCause.Cancelled)) { loginWithKakaoAccount( - context = context, onSuccess = onSuccess, onFailed = onFailed, ) @@ -39,15 +39,13 @@ class KakaoLoginHelper( ) } else { // 카카오톡 미설치 시 카카오계정 로그인 시도 loginWithKakaoAccount( - context = context, onSuccess = onSuccess, onFailed = onFailed, ) } } - private fun loginWithKakaoTalk( - context: Context, + override fun loginWithKakaoTalk( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -60,8 +58,7 @@ class KakaoLoginHelper( } } - private fun loginWithKakaoAccount( - context: Context, + override fun loginWithKakaoAccount( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -74,7 +71,7 @@ class KakaoLoginHelper( } } - fun logout() = runCatching { + override fun logout() = runCatching { UserApiClient.instance.logout { error -> if (error != null) { throw error @@ -82,7 +79,7 @@ class KakaoLoginHelper( } } - fun unlink() = runCatching { + override fun unlink() = runCatching { UserApiClient.instance.unlink { error -> if (error != null) { throw error From 10497e58437de608270205317c83fdde03997069 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 21 Dec 2023 23:47:45 +0900 Subject: [PATCH 07/60] =?UTF-8?q?fix:=20=EC=88=98=EC=88=98=20auth=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=88=84=EB=9D=BD=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/di/RepositoryModule.kt | 7 +++++++ data/src/main/java/com/susu/data/model/SnsProviders.kt | 5 +++++ .../com/susu/data/model/request/RefreshTokenRequest.kt | 8 ++++++++ 3 files changed, 20 insertions(+) create mode 100644 data/src/main/java/com/susu/data/model/SnsProviders.kt create mode 100644 data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt diff --git a/data/src/main/java/com/susu/data/di/RepositoryModule.kt b/data/src/main/java/com/susu/data/di/RepositoryModule.kt index e7158def..be606017 100644 --- a/data/src/main/java/com/susu/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/susu/data/di/RepositoryModule.kt @@ -1,6 +1,8 @@ package com.susu.data.di +import com.susu.data.repository.AuthRepositoryImpl import com.susu.data.repository.TokenRepositoryImpl +import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository import dagger.Binds import dagger.Module @@ -15,4 +17,9 @@ abstract class RepositoryModule { abstract fun bindTokenRepository( tokenRepositoryImpl: TokenRepositoryImpl, ): TokenRepository + + @Binds + abstract fun bindAuthRepository( + authRepositoryImpl: AuthRepositoryImpl, + ): AuthRepository } diff --git a/data/src/main/java/com/susu/data/model/SnsProviders.kt b/data/src/main/java/com/susu/data/model/SnsProviders.kt new file mode 100644 index 00000000..82968641 --- /dev/null +++ b/data/src/main/java/com/susu/data/model/SnsProviders.kt @@ -0,0 +1,5 @@ +package com.susu.data.model + +enum class SnsProviders(name: String) { + Kakao("KAKAO") +} diff --git a/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt b/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt new file mode 100644 index 00000000..b176c2ce --- /dev/null +++ b/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt @@ -0,0 +1,8 @@ +package com.susu.data.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class RefreshTokenRequest( + val refreshToken: String +) From 2f08e652cb4e4df5972638da38a9f3673d28ff01 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 21 Dec 2023 23:57:58 +0900 Subject: [PATCH 08/60] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=ED=95=9C=20api=20=EB=AA=85=EC=84=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/network/AuthService.kt | 2 ++ .../main/java/com/susu/data/repository/AuthRepository.kt | 4 +++- .../main/java/com/susu/domain/repository/AuthRepository.kt | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/susu/data/network/AuthService.kt b/data/src/main/java/com/susu/data/network/AuthService.kt index 86a306df..81020b62 100644 --- a/data/src/main/java/com/susu/data/network/AuthService.kt +++ b/data/src/main/java/com/susu/data/network/AuthService.kt @@ -9,6 +9,7 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query interface AuthService { @POST("oauth/{provider}/login") @@ -20,6 +21,7 @@ interface AuthService { @POST("oauth/{provider}/sign-up") suspend fun signUp( @Path("provider") provider: String, + @Query("accessToken") accessToken: String, @Body user: UserEntity, ): TokenEntity diff --git a/data/src/main/java/com/susu/data/repository/AuthRepository.kt b/data/src/main/java/com/susu/data/repository/AuthRepository.kt index 79038aff..f04a102c 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepository.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepository.kt @@ -23,10 +23,12 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun signUp( + snsAccessToken: String, user: User, ) = runCatching { authService.signUp( provider = SnsProviders.Kakao.name, + accessToken = snsAccessToken, user = user.toData(), ).toDomain() } @@ -46,7 +48,7 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun refreshAccessToken(refreshToken: String) = runCatching { authService.refreshAccessToken( - refreshTokenRequest = RefreshTokenRequest(refreshToken) + refreshTokenRequest = RefreshTokenRequest(refreshToken), ).toDomain() } } diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt index e1f914d9..5c68eb3b 100644 --- a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt @@ -4,9 +4,9 @@ import com.susu.core.model.Token import com.susu.core.model.User interface AuthRepository { - suspend fun login(snsAccessToken: String): Result - suspend fun signUp(user: User): Result - suspend fun canRegister(snsAccessToken: String): Boolean + suspend fun login(accessToken: String): Result + suspend fun signUp(snsAccessToken: String, user: User): Result + suspend fun canRegister(accessToken: String): Boolean suspend fun logout() suspend fun refreshAccessToken(refreshToken: String): Result } From 1af573553110255fc914a937bb780aaf06556518 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:00:50 +0900 Subject: [PATCH 09/60] =?UTF-8?q?fix:=20api=20=EB=B3=80=EA=B2=BD=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20user=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/model/src/main/java/com/susu/core/model/User.kt | 2 +- data/src/main/java/com/susu/data/model/UserEntity.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/model/src/main/java/com/susu/core/model/User.kt b/core/model/src/main/java/com/susu/core/model/User.kt index 395e82a9..30d34830 100644 --- a/core/model/src/main/java/com/susu/core/model/User.kt +++ b/core/model/src/main/java/com/susu/core/model/User.kt @@ -2,6 +2,6 @@ package com.susu.core.model data class User( val name: String, - val age: Int, + val gender: String, val birth: Int, ) diff --git a/data/src/main/java/com/susu/data/model/UserEntity.kt b/data/src/main/java/com/susu/data/model/UserEntity.kt index e4988673..038cb9e1 100644 --- a/data/src/main/java/com/susu/data/model/UserEntity.kt +++ b/data/src/main/java/com/susu/data/model/UserEntity.kt @@ -6,18 +6,18 @@ import kotlinx.serialization.Serializable @Serializable data class UserEntity( val name: String, - val age: Int, + val gender: String, val birth: Int, ) fun UserEntity.toDomain() = User( name = name, - age = age, + gender = gender, birth = birth, ) fun User.toData() = UserEntity( name = name, - age = age, + gender = gender, birth = birth, ) From 5c044161582ef2416eda58db1c801acd30ba2d7a Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:05:05 +0900 Subject: [PATCH 10/60] =?UTF-8?q?feat:=20SplashViewModel=20=EC=88=98?= =?UTF-8?q?=EC=88=98=20=ED=86=A0=ED=81=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/splash/SplashViewModel.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt index 6a21179d..44003b01 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt @@ -2,8 +2,9 @@ package com.susu.feature.loginsignup.splash import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository -import com.susu.feature.loginsignup.KakaoLoginHelper +import com.susu.domain.util.KakaoLoginProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -11,32 +12,35 @@ import javax.inject.Inject @HiltViewModel class SplashViewModel @Inject constructor( - private val kakaoLoginHelper: KakaoLoginHelper, + private val kakaoLoginProvider: KakaoLoginProvider, private val tokenRepository: TokenRepository, + private val authRepository: AuthRepository, ) : BaseViewModel(SplashContract.SplashState()) { init { viewModelScope.launch { // 1. 과거 카톡 로그인 이력 확인 - if (!kakaoLoginHelper.hasKakaoLoginHistory()) { + if (!kakaoLoginProvider.hasKakaoLoginHistory()) { // 1-1. 신규 유저 postSideEffect(SplashContract.SplashEffect.NavigateToSignUpVote) } else { // 2. 수수 토큰 여부 확인 - if (tokenRepository.getAccessToken().firstOrNull() == null && - tokenRepository.getRefreshToken().firstOrNull() == null - ) { + val accessToken = tokenRepository.getAccessToken().firstOrNull() + + if (accessToken == null) { // 2-1. 카카오 로그인 후 이탈한 유저 postSideEffect(SplashContract.SplashEffect.NavigateToSignUp) } else { // 3. 수수 토큰 만료 여부 확인 - if (TODO("수수 api 인증")) { - // 3-1. 수수 토큰 유효함. 자동로그인 - postSideEffect(SplashContract.SplashEffect.NavigateToReceived) - } else { - // 3-2. 수수 토큰 만료됨. - postSideEffect(SplashContract.SplashEffect.NavigateToLogIn) - } + authRepository.login(accessToken) + .onSuccess { + // 3-1. 자동 로그인 성공. + postSideEffect(SplashContract.SplashEffect.NavigateToReceived) + } + .onFailure { + // 3-2. 수수 토큰 만료됨. + postSideEffect(SplashContract.SplashEffect.NavigateToLogIn) + } } } } From f7cdfbdf941a8de6f5a6027d51135e128651583f Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:34:09 +0900 Subject: [PATCH 11/60] =?UTF-8?q?refactor:=20TokenRepository=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=8B=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20Token=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/data/repository/TokenRepositoryImpl.kt | 15 ++++----------- .../com/susu/domain/repository/TokenRepository.kt | 5 ++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt index e2073d9f..db2b1da1 100644 --- a/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt @@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey +import com.susu.core.model.Token import com.susu.data.extension.secureEdit import com.susu.data.extension.secureMap import com.susu.domain.repository.TokenRepository @@ -26,15 +27,12 @@ class TokenRepositoryImpl @Inject constructor( } } - override suspend fun saveAccessToken(accessToken: String) { - dataStore.secureEdit(accessToken) { preference, encrypted -> + override suspend fun saveTokens(token: Token) { + dataStore.secureEdit(token.accessToken) { preference, encrypted -> println(encrypted) preference[ACCESS_TOKEN] = encrypted } - } - - override suspend fun saveRefreshToken(refreshToken: String) { - dataStore.secureEdit(refreshToken) { preference, encrypted -> + dataStore.secureEdit(token.refreshToken) { preference, encrypted -> preference[REFRESH_TOKEN] = encrypted } } @@ -46,11 +44,6 @@ class TokenRepositoryImpl @Inject constructor( } } - override suspend fun refreshAccessToken(): String? { - TODO("Update Access Token by Refresh Token") - TODO("If token refresh failed, make user login again") - } - companion object { private val ACCESS_TOKEN = stringPreferencesKey("accessToken") private val REFRESH_TOKEN = stringPreferencesKey("refreshToken") diff --git a/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt b/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt index da0eafec..7fd8fb51 100644 --- a/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt @@ -1,12 +1,11 @@ package com.susu.domain.repository +import com.susu.core.model.Token import kotlinx.coroutines.flow.Flow interface TokenRepository { fun getAccessToken(): Flow fun getRefreshToken(): Flow - suspend fun saveAccessToken(accessToken: String) - suspend fun saveRefreshToken(refreshToken: String) + suspend fun saveTokens(token: Token) suspend fun deleteTokens() - suspend fun refreshAccessToken(): String? } From a08f9d719caeebab70c1b08c8c9b93518137f9ab Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:35:49 +0900 Subject: [PATCH 12/60] chore: AuthRepository.kt -> AuthRepositoryImpl.kt rename --- .../{AuthRepository.kt => AuthRepositoryImpl.kt} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename data/src/main/java/com/susu/data/repository/{AuthRepository.kt => AuthRepositoryImpl.kt} (87%) diff --git a/data/src/main/java/com/susu/data/repository/AuthRepository.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt similarity index 87% rename from data/src/main/java/com/susu/data/repository/AuthRepository.kt rename to data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index f04a102c..7e8d811a 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepository.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -14,11 +14,11 @@ class AuthRepositoryImpl @Inject constructor( private val authService: AuthService, ) : AuthRepository { override suspend fun login( - snsAccessToken: String, + accessToken: String, ) = kotlin.runCatching { authService.login( provider = SnsProviders.Kakao.name, - accessTokenRequest = AccessTokenRequest(snsAccessToken), + accessTokenRequest = AccessTokenRequest(accessToken), ).toDomain() } @@ -34,11 +34,11 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun canRegister( - snsAccessToken: String, + accessToken: String, ): Boolean { return authService.checkValidRegister( provider = SnsProviders.Kakao.name, - accessTokenRequest = AccessTokenRequest(snsAccessToken), + accessTokenRequest = AccessTokenRequest(accessToken), ).canRegister } From 323935e699f8ba48a5bad0a2512137d5662812ea Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:36:30 +0900 Subject: [PATCH 13/60] chore: KakaoLoginProvider.kt -> KakaoLoginProviderImpl.kt rename --- .../{KakaoLoginProvider.kt => KakaoLoginProviderImpl.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename feature/loginsignup/src/main/java/com/susu/feature/loginsignup/{KakaoLoginProvider.kt => KakaoLoginProviderImpl.kt} (100%) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProvider.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt similarity index 100% rename from feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProvider.kt rename to feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt From 732dba576e273092c560ef5e97e0e7bd2832fbab Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 00:37:19 +0900 Subject: [PATCH 14/60] =?UTF-8?q?feat:=20LoginContract,=20LoginViewModel?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/login/LoginContract.kt | 14 ++++++ .../loginsignup/login/LoginViewModel.kt | 43 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt new file mode 100644 index 00000000..655b9658 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -0,0 +1,14 @@ +package com.susu.feature.loginsignup.login + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +class LoginContract { + sealed class LoginEffect : SideEffect { + data object NavigateToReceived : LoginEffect() + data object NavigateToSignUp : LoginEffect() + data class ExitProcess(val error: Throwable?) : LoginEffect() + } + + object LoginState : UiState +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt new file mode 100644 index 00000000..a1d92b81 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -0,0 +1,43 @@ +package com.susu.feature.loginsignup.login + +import androidx.lifecycle.viewModelScope +import com.kakao.sdk.auth.TokenManagerProvider +import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.repository.AuthRepository +import com.susu.domain.repository.TokenRepository +import com.susu.domain.util.KakaoLoginProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val kakaoLoginProvider: KakaoLoginProvider, + private val authRepository: AuthRepository, + private val tokenRepository: TokenRepository, +) : BaseViewModel(LoginContract.LoginState) { + + fun login() { + kakaoLoginProvider.login( + onSuccess = { accessToken -> + if (TokenManagerProvider.instance.manager.getToken() == null) { + // 카카오 로그인 이력이 없을 때만 회원 가입 페이지 이동 + postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) + } else { + viewModelScope.launch { + authRepository.login(accessToken) + .onSuccess { token -> + tokenRepository.saveTokens(token) + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + }.onFailure { + // TODO: 음....... 수수 서버 오류? + } + } + } + }, + onFailed = { + postSideEffect(LoginContract.LoginEffect.ExitProcess(it)) + }, + ) + } +} From e6ae3c371b1f437e956233d49f27bd79492b51df Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 01:30:55 +0900 Subject: [PATCH 15/60] =?UTF-8?q?feat:=20Kakao=20di=20Module=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/feature/loginsignup/di/KakaoModule.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt new file mode 100644 index 00000000..97fb3ad3 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt @@ -0,0 +1,18 @@ +package com.susu.feature.loginsignup.di + +import com.susu.domain.util.KakaoLoginProvider +import com.susu.feature.loginsignup.KakaoLoginProviderImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +abstract class KakaoModule { + + @Binds + abstract fun bindKakaoLoginProvider( + kakaoLoginProviderImpl: KakaoLoginProviderImpl + ): KakaoLoginProvider +} From 0a42e766dc216209de9e196131780bc15ae3454f Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 01:31:49 +0900 Subject: [PATCH 16/60] =?UTF-8?q?chore:=20feature=20plugin=EC=97=90=20hilt?= =?UTF-8?q?.navigation.compose=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feature 모듈에서 hiltViewModel() 사용을 위해 추가했습니다. --- .../convention/src/main/java/FeatureComposeConventionPlugin.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt b/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt index 7d0374b0..16d02087 100644 --- a/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt @@ -26,6 +26,8 @@ internal class FeatureComposeConventionPlugin : Plugin { "implementation"(libs.findLibrary("kotlinx.coroutines.android").get()) "implementation"(libs.findLibrary("kotlinx.coroutines.core").get()) + "implementation"(libs.findLibrary("hilt.navigation.compose").get()) + "androidTestImplementation"(libs.findLibrary("junit").get()) "implementation"(libs.findLibrary("timber").get()) } From 230df7247d82f0d56ed0a49d68a0bdc74409b8f2 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 12:23:37 +0900 Subject: [PATCH 17/60] =?UTF-8?q?fix:=20Repository=20di=20=EB=B6=88?= =?UTF-8?q?=EA=B0=80=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit data module에 액세스 불가하여 Repository를 주입하지 못하는 문제 --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 945f42c6..343f5c91 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ android { dependencies { implementation(projects.feature.navigator) + implementation(projects.data) implementation(libs.kakao.sdk.user) implementation(platform(libs.firebase.bom)) From 2b9d07eb045bd0012091ca109d04d62500854f87 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 13:16:36 +0900 Subject: [PATCH 18/60] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20acc?= =?UTF-8?q?essToken=20=EC=A0=91=EA=B7=BC=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/susu/domain/util/KakaoLoginProvider.kt | 1 + .../com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt index 24847bfb..0e99542e 100644 --- a/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt +++ b/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt @@ -2,6 +2,7 @@ package com.susu.domain.util interface KakaoLoginProvider { fun hasKakaoLoginHistory(): Boolean + fun getAccessToken(): String? fun login( onSuccess: (String) -> Unit, diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt index 0242d367..5a3a37df 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt @@ -2,6 +2,7 @@ package com.susu.feature.loginsignup import android.content.Context import com.kakao.sdk.auth.AuthApiClient +import com.kakao.sdk.auth.TokenManagerProvider import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause @@ -20,6 +21,10 @@ class KakaoLoginProviderImpl @Inject constructor( return AuthApiClient.instance.hasToken() } + override fun getAccessToken(): String? { + return TokenManagerProvider.instance.manager.getToken()?.accessToken + } + override fun login( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, From 2972865244b777b64fc6f15e16d34fbaa3131f09 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 13:17:04 +0900 Subject: [PATCH 19/60] =?UTF-8?q?refactor:=20Splash=EC=99=80=20Login?= =?UTF-8?q?=EC=9D=84=20Login=EC=97=90=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/login/LoginContract.kt | 5 +- .../feature/loginsignup/login/LoginScreen.kt | 42 ++++++++++++++++ .../loginsignup/login/LoginViewModel.kt | 48 +++++++++++++------ .../loginsignup/splash/SplashContract.kt | 18 ------- .../loginsignup/splash/SplashViewModel.kt | 48 ------------------- 5 files changed, 80 insertions(+), 81 deletions(-) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt index 655b9658..cdb63821 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -10,5 +10,8 @@ class LoginContract { data class ExitProcess(val error: Throwable?) : LoginEffect() } - object LoginState : UiState + data class LoginState( + val isLoading: Boolean = false, + val showVote: Boolean = false + ) : UiState } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt new file mode 100644 index 00000000..f6b59a17 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -0,0 +1,42 @@ +package com.susu.feature.loginsignup.login + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Button +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel + +@Composable +fun SplashScreen( + viewModel: LoginViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsState() + + LaunchedEffect(key1 = viewModel.sideEffect) { + viewModel.sideEffect.collect { sideEffect -> + when (sideEffect) { + is LoginContract.LoginEffect.ExitProcess -> TODO() + LoginContract.LoginEffect.NavigateToReceived -> TODO() + LoginContract.LoginEffect.NavigateToSignUp -> TODO() + } + } + } +} + +@Composable +fun SplashVote(onClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxSize() + ) { + Button( + onClick = onClick + ) { + Text(text = "신규 유저 투표 UI") + } + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index a1d92b81..0b9d0276 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -1,7 +1,6 @@ package com.susu.feature.loginsignup.login import androidx.lifecycle.viewModelScope -import com.kakao.sdk.auth.TokenManagerProvider import com.susu.core.ui.base.BaseViewModel import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository @@ -15,23 +14,40 @@ class LoginViewModel @Inject constructor( private val kakaoLoginProvider: KakaoLoginProvider, private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, -) : BaseViewModel(LoginContract.LoginState) { +) : BaseViewModel(LoginContract.LoginState()) { + + init { + viewModelScope.launch { + // 1. 과거 카톡 로그인 이력 확인 + if (!kakaoLoginProvider.hasKakaoLoginHistory()) { + // 1-1. 신규 유저 + intent { copy(showVote = true) } + } else { + // 2. 카카오 access token 존재 시 로그인 시도 + kakaoLoginProvider.getAccessToken()?.let { + authRepository.login(it).onSuccess { + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + } + } + // 3. 카카오 로그인 필요 + } + } + } fun login() { kakaoLoginProvider.login( onSuccess = { accessToken -> - if (TokenManagerProvider.instance.manager.getToken() == null) { - // 카카오 로그인 이력이 없을 때만 회원 가입 페이지 이동 - postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) - } else { - viewModelScope.launch { - authRepository.login(accessToken) - .onSuccess { token -> - tokenRepository.saveTokens(token) - postSideEffect(LoginContract.LoginEffect.NavigateToReceived) - }.onFailure { - // TODO: 음....... 수수 서버 오류? - } + viewModelScope.launch { + // 수수 서버에 가입되지 않은 회원이라면 -> 회원 정보 기입 후 수수 회원가입 + if (authRepository.canRegister(accessToken)) { + postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) + } else { + authRepository.login(accessToken).onSuccess { token -> + tokenRepository.saveTokens(token) + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + }.onFailure { + postSideEffect(LoginContract.LoginEffect.ExitProcess(it)) + } } } }, @@ -40,4 +56,8 @@ class LoginViewModel @Inject constructor( }, ) } + + fun selectSignUpVote() { + intent { copy(showVote = false) } + } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt deleted file mode 100644 index ef7a284a..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashContract.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.susu.feature.loginsignup.splash - -import com.susu.core.ui.base.SideEffect -import com.susu.core.ui.base.UiState - -class SplashContract { - sealed class SplashEffect : SideEffect { - data object NavigateToSignUpVote : SplashEffect() - data object NavigateToLogIn : SplashEffect() - data object NavigateToReceived : SplashEffect() - data object NavigateToSignUp : SplashEffect() - } - - data class SplashState( - val isLoading: Boolean = false, - val selectedIndex: Int = 0, // 온보딩 투표 선택한 항목 - ) : UiState -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt deleted file mode 100644 index 44003b01..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/splash/SplashViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.susu.feature.loginsignup.splash - -import androidx.lifecycle.viewModelScope -import com.susu.core.ui.base.BaseViewModel -import com.susu.domain.repository.AuthRepository -import com.susu.domain.repository.TokenRepository -import com.susu.domain.util.KakaoLoginProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class SplashViewModel @Inject constructor( - private val kakaoLoginProvider: KakaoLoginProvider, - private val tokenRepository: TokenRepository, - private val authRepository: AuthRepository, -) : BaseViewModel(SplashContract.SplashState()) { - - init { - viewModelScope.launch { - // 1. 과거 카톡 로그인 이력 확인 - if (!kakaoLoginProvider.hasKakaoLoginHistory()) { - // 1-1. 신규 유저 - postSideEffect(SplashContract.SplashEffect.NavigateToSignUpVote) - } else { - // 2. 수수 토큰 여부 확인 - val accessToken = tokenRepository.getAccessToken().firstOrNull() - - if (accessToken == null) { - // 2-1. 카카오 로그인 후 이탈한 유저 - postSideEffect(SplashContract.SplashEffect.NavigateToSignUp) - } else { - // 3. 수수 토큰 만료 여부 확인 - authRepository.login(accessToken) - .onSuccess { - // 3-1. 자동 로그인 성공. - postSideEffect(SplashContract.SplashEffect.NavigateToReceived) - } - .onFailure { - // 3-2. 수수 토큰 만료됨. - postSideEffect(SplashContract.SplashEffect.NavigateToLogIn) - } - } - } - } - } -} From 765bc7f3c3de87662b1f801421ad193b2ff805c8 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 13:20:09 +0900 Subject: [PATCH 20/60] =?UTF-8?q?fix:=20Authenticator=EB=A5=BC=20token=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=ED=96=89=EC=9D=84=20authRepository=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=ED=95=A8=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/data/network/TokenAuthenticator.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt index ce7318f9..ccc6e76e 100644 --- a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt +++ b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt @@ -1,5 +1,6 @@ package com.susu.data.network +import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking @@ -11,6 +12,7 @@ import javax.inject.Inject class TokenAuthenticator @Inject constructor( private val tokenRepository: TokenRepository, + private val authRepository: AuthRepository, ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { // 1. refresh token으로 갱신 요청 @@ -23,7 +25,7 @@ class TokenAuthenticator @Inject constructor( return runBlocking { // 2. access token 갱신 - val refreshedAccessToken = tokenRepository.refreshAccessToken() + val refreshedAccessToken = authRepository.refreshAccessToken(refreshToken).getOrNull() // 2-1. 정상적으로 받지 못하면 request token 까지 만료된 것. if (refreshedAccessToken == null) { @@ -34,7 +36,7 @@ class TokenAuthenticator @Inject constructor( } else { // 3. 헤더에 토큰을 교체한 request 생성 response.request.newBuilder() - .header("Authorization", "Bearer $refreshedAccessToken") + .header("Authorization", "Bearer ${refreshedAccessToken.accessToken}") .build() } } From 9e17f102636cca7375fa7b7d851f143d80fd7dbb Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 15:56:20 +0900 Subject: [PATCH 21/60] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/login/LoginContract.kt | 2 +- .../feature/loginsignup/login/LoginScreen.kt | 59 ++++++++++++++---- .../src/main/res/drawable/btn_kakao_login.png | Bin 0 -> 3307 bytes 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 feature/loginsignup/src/main/res/drawable/btn_kakao_login.png diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt index cdb63821..ad34134c 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -7,7 +7,7 @@ class LoginContract { sealed class LoginEffect : SideEffect { data object NavigateToReceived : LoginEffect() data object NavigateToSignUp : LoginEffect() - data class ExitProcess(val error: Throwable?) : LoginEffect() + data class ShowToast(val msg: String) : LoginEffect() } data class LoginState( diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index f6b59a17..3c7bf50e 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -1,42 +1,77 @@ package com.susu.feature.loginsignup.login +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Button -import androidx.compose.material3.Surface +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.hilt.navigation.compose.hiltViewModel +import com.susu.feature.loginsignup.R @Composable -fun SplashScreen( +fun LoginScreen( viewModel: LoginViewModel = hiltViewModel(), ) { + val context = LocalContext.current val uiState by viewModel.uiState.collectAsState() LaunchedEffect(key1 = viewModel.sideEffect) { viewModel.sideEffect.collect { sideEffect -> when (sideEffect) { - is LoginContract.LoginEffect.ExitProcess -> TODO() - LoginContract.LoginEffect.NavigateToReceived -> TODO() - LoginContract.LoginEffect.NavigateToSignUp -> TODO() + is LoginContract.LoginEffect.ShowToast -> { + Toast.makeText(context, sideEffect.msg, Toast.LENGTH_SHORT).show() + } + + LoginContract.LoginEffect.NavigateToReceived -> { + Toast.makeText(context, "기존 회원 로그인 성공", Toast.LENGTH_SHORT).show() + } + + LoginContract.LoginEffect.NavigateToSignUp -> { + Toast.makeText(context, "신규 회원 가입 창으로", Toast.LENGTH_SHORT).show() + } } } } + + Box( + modifier = Modifier.fillMaxSize() + ) { + if (uiState.showVote) { + SplashVote(onClick = viewModel::selectSignUpVote) + } else { + KakaoLogin(onClick = viewModel::login) + } + + if (uiState.isLoading) { + CircularProgressIndicator() + } + } } @Composable fun SplashVote(onClick: () -> Unit) { - Surface( - modifier = Modifier.fillMaxSize() + Button( + onClick = onClick, + modifier = Modifier.wrapContentHeight(), ) { - Button( - onClick = onClick - ) { - Text(text = "신규 유저 투표 UI") - } + Text(text = "신규 유저 투표 UI") + } +} + +@Composable +fun KakaoLogin(onClick: () -> Unit) { + Button(onClick = onClick, modifier = Modifier.wrapContentHeight().fillMaxWidth()) { + Image(painter = painterResource(id = R.drawable.btn_kakao_login), contentDescription = null) } } diff --git a/feature/loginsignup/src/main/res/drawable/btn_kakao_login.png b/feature/loginsignup/src/main/res/drawable/btn_kakao_login.png new file mode 100644 index 0000000000000000000000000000000000000000..c882acc7311d61cc8eab3b316e0b90c64de18dfc GIT binary patch literal 3307 zcmV

5Q;P)Px>rAb6VRCodHT?u#;MHc>Nau7nu!4+Zv0YOk%Sr8)OKIB$GMZPbru((1NvjPjB zq9z<3h#FXR7g-lkIagr?ky93hRW3nL7iCo>g0LbIjwIwhXX|yRdU`VVY=-nOufA`l zs;lZ%SN;9^_3P?auS^i3>MSf28zq=bNHrO)QxY7rOuWAXxUP#ls{2^$*dNk;@64}< z#w)}vx+f@c-!aXUII&v-vVIKpsbS+@O2KG{d>WFUiAu~iBq{APo?>M^q29Im%OLH2Hu`i$2O#>thdG{Fr7l&OiBwIP` zsR1(W1cQ`XGJfOjtKZAHi(-_MzPzlV&BeV8H7S9uW=dMG^+tBlk!qxD4qRbaXf4f!_o;>y@P4ZWZZx~dG87nj@)EU8~nO!GGW z4iyzv{UpD@t~(x`zPQBNhDQWGR*U+sJq9Zn`i`{CaYI<0g|jzfIbE#?&iX(XM?FNE=_E9G8`u zaQ2rFt!z+AQ_P)y1--giwR)}44+&Nsya3haHuQonD62A9UR-vwP~#aeH-ORWj`9x8 z?}l!W;$p865K7-}ei(WU8xwef0E>1{P~tjXBj7P(&B+8h4N!lS=D-NJLcrytQ0QMs z?COfVZawvgh`Jz(um$zco{VHBWEX89&q1JHvg0x%=C0ckiR?>eL`PNO z>6!UT7Z)v!5)5O(_oqV9<(4vhn-!*Y{jLX_V9WB0*tjD?W3uxXLh!fMQJ7LaYebdl zCOon&TcfKVq0Tl9=~n{!$c6k*GFRes*75uNn(v2T8x%S&2eJyc|_J%KcMO*maM#Z zc@(BTs7^!5JbgT3LKB;|^<|VUC^RFgf!coCUI{q4-wG^AH$4;9qz$BFj~Hv#d?0gn zAPvjAzE)P65E7zF7(HcCu~}m>eLkn%Bj6EZxo?JRFaov+c>GpqOU6fxfNKQ!Glgq> zxYP#(l$m#;6Xt%CMf8$*v$0wg_|PDe1UCYTjco#wMQkxy&SIBPTL%U=Bs}`sM1Vfl zv+`b>h;myB0wlSZv6vbBf&jl2`UO^Qlo^Y8z%K|eWBCPEZqy^j`uba7XbRA(yBZ(+ zj)7wYTqEFCc_(J7sIYG&RMUsbU~E(Td_Dw)MJDW7e*tZ;x87}J9Wmo++f`tv52{uRvome;@90y4{>i0S`ECQG zzZi|-14==*Uy~k-6Y;fSi$&Mnr9GA_rXpf@2W7R7;c8@5MTKcd>EK=4xczJoAY9XqxBh!syRa>m@vO-Z+Wy1Px_3_|UZ~`OG zf3;$ljJ5hN!1!s-yF%rG7Czg09>0Gg4u`f_EjGzQltT8!5asbC$FyQ(@@Tpw<8b1_J!R zC1VZh18iOa+&$5Llpm}CI^SZSZ}glSdM9A}%8O``VAYYR&r_a^LtIRiQ$6R^`8oLF zSTlzv#CHuY2K}D>b2R3Nh3w8k`!*_5$@2gH9I8pWF(Zp*_B9c^RNiTXMPj_|dIAsJ z2h5!TVOD!hj9VK-JHcqAyw?o$C?w^bY}vQ}E23sGnl`FJ?<7?x)B8NrSM1lL6eSLP zcC>C;spxmp=EkCHCo8jE4Zmpm{`oWQma&#B2Yx&D+G8jQsx$Lr(tl`y1qa^{aLZV} zHOo}Nj(|rq@4*fdkKcO)n6bRaj43=ofEmjJkhqHX2ry%Lj~P?AL4Yhs&9tJzO+7AV z1iV8)ET}M>5-~_{SgQCrUpMYuvs^n!5m0R1X(0uz(jg2i7m$E5tP!&|1A`P39yTMu z2pALrMXL(O1E!`pM_W@&7GEu1H)vnETZ{lBzz8S^C>pyESoyrLo;iXMU<8bg08KBL zSy&nUGI9SH0Y-ojAX&wqbC?$x0Y)HT5MY57Fqeuu$_Ow5EU=gd7y(8gU=VP*pwQD% zoIEXd7Wk`Je7Ms+#oa@IS!obX58T(~8jJuVzzFCO_`fu)PsQd3GtOSZOa1^Y*z}LKSR?pV$rr$B{DN|B?!jzbrK|3FKp+1=e?2wKMEv z$5~+24w8;r`9*Bi*}hE$E(?w4a;`~!a@M3QDmLNz#A=#6qBG;<=@1N? z?VN@L;~$@j!7ESYVa!OY>$&KL7K|KJto+73(iqPPH+ST($59}`MncJ1AV;NbTiN>J z=_TB?(9cT=WLO4JeR9E@REf)_e3Bx)PnxPM^l8@pLqS!OBnlbW4tR|6Al&* zR{QlR6CUi$ing|CX9OBXS@7l})jy|fLmyWO4~L|ApE+djOG;nQ({#Sj)=1d45ZaRr ze_xz~xr?I37P<+DiMAj|2=QAwRNxcgOjhC~>vC*71bh})A;JgizD>ZI4Zsr%Abe#})~9$#-x8aA^1UiOL{oY4sAfnRRfLRbSCk*+xeBByk-|8zKd6PbN zJPZqGTVDY~#2e#n!jrWpVc+3!go(Z4dvw*;x8Rva^Ms}*JYgr5p(mVb zwdQMWjF^u);9wHnEEw#%cp#|@kRm4Ad+~hLRlU;x5jXsP;kJHKN}*Gygf!`%nUSY> z5~t3s-q1j}wlBBSsAR`!(tZ5&IGj453bI`B+L$VQ1lJ5SPZr;#(niCfI=|{zZs(r$ z`B$Uyy_nC`E>2J5Dt&NMw$Q57Cu`eYV$A8budl9X zxy%>{)QZ5`y-&EiohrWkiC4To%>(-OFvj4x7oH(dQ((EHswuGC)i+}KjHhE!UT*(| zQ1+GfAbDx7RkM@%9M>%ZwIZkMu)qqKOT`^! z1Q-DpSj+>A03#4E2#}v-_PHG}my0{f2m~(ziUU6}{F%WE0gs#!U<3jPftkv8UEYKu pfG!z#l@SOs1cZ>002ovPDHLkV1oS~Dl`B9 literal 0 HcmV?d00001 From 5f72d060a0fb7eaf8729bd9318a532ee49a1cb7b Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 15:57:17 +0900 Subject: [PATCH 22/60] =?UTF-8?q?fix:=20hilt=20=EC=88=9C=ED=99=98=20?= =?UTF-8?q?=EC=B0=B8=EC=A1=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/NetworkModule.kt | 47 ++++++++++++++++++- .../main/java/com/susu/data/di/Qualifier.kt | 11 +++++ .../java/com/susu/data/network/ApiService.kt | 35 ++++++++++++++ .../java/com/susu/data/network/AuthService.kt | 31 +----------- .../susu/data/network/TokenAuthenticator.kt | 10 ++-- .../data/repository/AuthRepositoryImpl.kt | 19 +++----- .../susu/domain/repository/AuthRepository.kt | 1 - 7 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 data/src/main/java/com/susu/data/di/Qualifier.kt create mode 100644 data/src/main/java/com/susu/data/network/ApiService.kt diff --git a/data/src/main/java/com/susu/data/di/NetworkModule.kt b/data/src/main/java/com/susu/data/di/NetworkModule.kt index 30758c99..1dbfd35c 100644 --- a/data/src/main/java/com/susu/data/di/NetworkModule.kt +++ b/data/src/main/java/com/susu/data/di/NetworkModule.kt @@ -4,6 +4,7 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.susu.data.Constants.RETROFIT_TAG import com.susu.data.extension.isJsonArray import com.susu.data.extension.isJsonObject +import com.susu.data.network.ApiService import com.susu.data.network.AuthService import com.susu.data.network.TokenAuthenticator import com.susu.data.network.TokenInterceptor @@ -66,6 +67,44 @@ object NetworkModule { .client(okHttpClient) .build() + @Singleton + @Provides + @AuthOkHttpClient + fun provideAuthOkHttpClient( + json: Json, + ): OkHttpClient { + val loggingInterceptor = HttpLoggingInterceptor { message -> + when { + !message.isJsonObject() && !message.isJsonArray() -> + Timber.tag(RETROFIT_TAG).d("CONNECTION INFO -> $message") + + else -> kotlin.runCatching { + json.encodeToString(Json.parseToJsonElement(message)) + }.onSuccess { + Timber.tag(RETROFIT_TAG).d(it) + }.onFailure { + Timber.tag(RETROFIT_TAG).d(message) + } + } + } + + return OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + } + + @Singleton + @Provides + @AuthRetrofit + fun provideAuthRetrofit( + @AuthOkHttpClient okHttpClient: OkHttpClient, + json: Json, + ): Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .client(okHttpClient) + .build() + @Singleton @Provides fun provideJson(): Json { @@ -78,7 +117,13 @@ object NetworkModule { @Singleton @Provides - fun provideAuthService(retrofit: Retrofit): AuthService { + fun provideApiService(retrofit: Retrofit): ApiService { + return retrofit.create(ApiService::class.java) + } + + @Singleton + @Provides + fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthService { return retrofit.create(AuthService::class.java) } } diff --git a/data/src/main/java/com/susu/data/di/Qualifier.kt b/data/src/main/java/com/susu/data/di/Qualifier.kt new file mode 100644 index 00000000..f14a1699 --- /dev/null +++ b/data/src/main/java/com/susu/data/di/Qualifier.kt @@ -0,0 +1,11 @@ +package com.susu.data.di + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +internal annotation class AuthOkHttpClient + +@Qualifier +@Retention(AnnotationRetention.BINARY) +internal annotation class AuthRetrofit diff --git a/data/src/main/java/com/susu/data/network/ApiService.kt b/data/src/main/java/com/susu/data/network/ApiService.kt new file mode 100644 index 00000000..49114f08 --- /dev/null +++ b/data/src/main/java/com/susu/data/network/ApiService.kt @@ -0,0 +1,35 @@ +package com.susu.data.network + +import com.susu.data.model.TokenEntity +import com.susu.data.model.UserEntity +import com.susu.data.model.request.AccessTokenRequest +import com.susu.data.model.response.ValidRegisterResponse +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface ApiService { + @POST("oauth/{provider}/login") + suspend fun login( + @Path("provider") provider: String, + @Body accessTokenRequest: AccessTokenRequest, + ): TokenEntity + + @POST("oauth/{provider}/sign-up") + suspend fun signUp( + @Path("provider") provider: String, + @Query("accessToken") accessToken: String, + @Body user: UserEntity, + ): TokenEntity + + @GET("oauth/{provider}/sign-up/valid") + suspend fun checkValidRegister( + @Path("provider") provider: String, + @Body accessTokenRequest: AccessTokenRequest, + ): ValidRegisterResponse + + @POST("auth/logout") + suspend fun logout() +} diff --git a/data/src/main/java/com/susu/data/network/AuthService.kt b/data/src/main/java/com/susu/data/network/AuthService.kt index 81020b62..0c54f2e9 100644 --- a/data/src/main/java/com/susu/data/network/AuthService.kt +++ b/data/src/main/java/com/susu/data/network/AuthService.kt @@ -1,41 +1,14 @@ package com.susu.data.network import com.susu.data.model.TokenEntity -import com.susu.data.model.UserEntity -import com.susu.data.model.request.AccessTokenRequest import com.susu.data.model.request.RefreshTokenRequest -import com.susu.data.model.response.ValidRegisterResponse +import retrofit2.Response import retrofit2.http.Body -import retrofit2.http.GET import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Query interface AuthService { - @POST("oauth/{provider}/login") - suspend fun login( - @Path("provider") provider: String, - @Body accessTokenRequest: AccessTokenRequest, - ): TokenEntity - - @POST("oauth/{provider}/sign-up") - suspend fun signUp( - @Path("provider") provider: String, - @Query("accessToken") accessToken: String, - @Body user: UserEntity, - ): TokenEntity - - @GET("oauth/{provider}/sign-up/valid") - suspend fun checkValidRegister( - @Path("provider") provider: String, - @Body accessTokenRequest: AccessTokenRequest, - ): ValidRegisterResponse - - @POST("auth/logout") - suspend fun logout() - @POST("auth/token/refresh") suspend fun refreshAccessToken( @Body refreshTokenRequest: RefreshTokenRequest, - ): TokenEntity + ): Response } diff --git a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt index ccc6e76e..3be9038c 100644 --- a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt +++ b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt @@ -1,6 +1,6 @@ package com.susu.data.network -import com.susu.domain.repository.AuthRepository +import com.susu.data.model.request.RefreshTokenRequest import com.susu.domain.repository.TokenRepository import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking @@ -12,7 +12,7 @@ import javax.inject.Inject class TokenAuthenticator @Inject constructor( private val tokenRepository: TokenRepository, - private val authRepository: AuthRepository, + private val authService: AuthService ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { // 1. refresh token으로 갱신 요청 @@ -25,10 +25,10 @@ class TokenAuthenticator @Inject constructor( return runBlocking { // 2. access token 갱신 - val refreshedAccessToken = authRepository.refreshAccessToken(refreshToken).getOrNull() + val tokenResponse = authService.refreshAccessToken(RefreshTokenRequest(refreshToken)) // 2-1. 정상적으로 받지 못하면 request token 까지 만료된 것. - if (refreshedAccessToken == null) { + if (tokenResponse.isSuccessful.not() || tokenResponse.body() == null) { // 삭제하여 다시 로그인하도록 유도 tokenRepository.deleteTokens() response.close() @@ -36,7 +36,7 @@ class TokenAuthenticator @Inject constructor( } else { // 3. 헤더에 토큰을 교체한 request 생성 response.request.newBuilder() - .header("Authorization", "Bearer ${refreshedAccessToken.accessToken}") + .header("Authorization", "Bearer ${tokenResponse.body()!!.accessToken}") .build() } } diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 7e8d811a..68d0cb77 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -3,20 +3,19 @@ package com.susu.data.repository import com.susu.core.model.User import com.susu.data.model.SnsProviders import com.susu.data.model.request.AccessTokenRequest -import com.susu.data.model.request.RefreshTokenRequest import com.susu.data.model.toData import com.susu.data.model.toDomain -import com.susu.data.network.AuthService +import com.susu.data.network.ApiService import com.susu.domain.repository.AuthRepository import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( - private val authService: AuthService, + private val apiService: ApiService, ) : AuthRepository { override suspend fun login( accessToken: String, ) = kotlin.runCatching { - authService.login( + apiService.login( provider = SnsProviders.Kakao.name, accessTokenRequest = AccessTokenRequest(accessToken), ).toDomain() @@ -26,7 +25,7 @@ class AuthRepositoryImpl @Inject constructor( snsAccessToken: String, user: User, ) = runCatching { - authService.signUp( + apiService.signUp( provider = SnsProviders.Kakao.name, accessToken = snsAccessToken, user = user.toData(), @@ -36,19 +35,13 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun canRegister( accessToken: String, ): Boolean { - return authService.checkValidRegister( + return apiService.checkValidRegister( provider = SnsProviders.Kakao.name, accessTokenRequest = AccessTokenRequest(accessToken), ).canRegister } override suspend fun logout() { - authService.logout() - } - - override suspend fun refreshAccessToken(refreshToken: String) = runCatching { - authService.refreshAccessToken( - refreshTokenRequest = RefreshTokenRequest(refreshToken), - ).toDomain() + apiService.logout() } } diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt index 5c68eb3b..18dd114d 100644 --- a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt @@ -8,5 +8,4 @@ interface AuthRepository { suspend fun signUp(snsAccessToken: String, user: User): Result suspend fun canRegister(accessToken: String): Boolean suspend fun logout() - suspend fun refreshAccessToken(refreshToken: String): Result } From 6960045f8953172e9b695b7a032abe1ecc59197c Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 15:58:47 +0900 Subject: [PATCH 23/60] =?UTF-8?q?chore:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/KakaoLoginProviderImpl.kt | 8 ++++--- .../feature/loginsignup/di/KakaoModule.kt | 6 +++-- .../loginsignup/login/LoginViewModel.kt | 22 ++++++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt index 5a3a37df..47807671 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt @@ -9,12 +9,11 @@ import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient import com.susu.domain.util.KakaoLoginProvider import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton class KakaoLoginProviderImpl @Inject constructor( - @ApplicationContext private val context: Context, + @ApplicationContext val context: Context, ) : KakaoLoginProvider { override fun hasKakaoLoginHistory(): Boolean { @@ -30,6 +29,7 @@ class KakaoLoginProviderImpl @Inject constructor( onFailed: (Throwable?) -> Unit, ) { if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + Timber.tag("AUTH").d("카카오톡 설치 확인") loginWithKakaoTalk( onSuccess = onSuccess, onFailed = { error -> @@ -43,6 +43,7 @@ class KakaoLoginProviderImpl @Inject constructor( }, ) } else { // 카카오톡 미설치 시 카카오계정 로그인 시도 + Timber.tag("AUTH").d("카카오톡 미설치") loginWithKakaoAccount( onSuccess = onSuccess, onFailed = onFailed, @@ -55,6 +56,7 @@ class KakaoLoginProviderImpl @Inject constructor( onFailed: (Throwable?) -> Unit, ) { UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> + Timber.tag("AUTH").d("카카오톡 로그인 결과 $token $error") if (token != null) { onSuccess(token.accessToken) } else { diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt index 97fb3ad3..761f6b01 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt @@ -6,13 +6,15 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton -@InstallIn(SingletonComponent::class) @Module +@InstallIn(SingletonComponent::class) abstract class KakaoModule { @Binds - abstract fun bindKakaoLoginProvider( + @Singleton + abstract fun bindsKakaoLoginProvider( kakaoLoginProviderImpl: KakaoLoginProviderImpl ): KakaoLoginProvider } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index 0b9d0276..76603754 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -7,6 +7,7 @@ import com.susu.domain.repository.TokenRepository import com.susu.domain.util.KakaoLoginProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -18,16 +19,22 @@ class LoginViewModel @Inject constructor( init { viewModelScope.launch { + intent { copy(isLoading = true) } + Timber.tag("AUTH").d("카카오 로그인 이력 확인") // 1. 과거 카톡 로그인 이력 확인 if (!kakaoLoginProvider.hasKakaoLoginHistory()) { // 1-1. 신규 유저 - intent { copy(showVote = true) } + intent { copy(isLoading = false, showVote = true) } + Timber.tag("AUTH").d("신규유져") } else { // 2. 카카오 access token 존재 시 로그인 시도 + Timber.d("수수 로그인 시도") kakaoLoginProvider.getAccessToken()?.let { authRepository.login(it).onSuccess { + Timber.tag("AUTH").d("수수 로그인 성공") postSideEffect(LoginContract.LoginEffect.NavigateToReceived) } + intent { copy(isLoading = false) } } // 3. 카카오 로그인 필요 } @@ -35,24 +42,33 @@ class LoginViewModel @Inject constructor( } fun login() { + Timber.tag("AUTH").d("카카오 로그인 시도") kakaoLoginProvider.login( onSuccess = { accessToken -> + Timber.tag("AUTH").d("카카오 로그인 성공") viewModelScope.launch { + intent { copy(isLoading = true) } // 수수 서버에 가입되지 않은 회원이라면 -> 회원 정보 기입 후 수수 회원가입 if (authRepository.canRegister(accessToken)) { + Timber.tag("AUTH").d("수수 가입 가능") postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) } else { authRepository.login(accessToken).onSuccess { token -> + Timber.tag("AUTH").d("수수 로그인 성공") tokenRepository.saveTokens(token) postSideEffect(LoginContract.LoginEffect.NavigateToReceived) }.onFailure { - postSideEffect(LoginContract.LoginEffect.ExitProcess(it)) + Timber.tag("AUTH").d("수수 로그인 실패 ${it.message}") + postSideEffect(LoginContract.LoginEffect.ShowToast(it.message ?: "수수 로그인 실패")) } } + intent { copy(isLoading = false) } } }, onFailed = { - postSideEffect(LoginContract.LoginEffect.ExitProcess(it)) + Timber.tag("AUTH").d("카카오 로그인 실패 ${it?.message}") + postSideEffect(LoginContract.LoginEffect.ShowToast(it?.message ?: "카카오 로그인 실패")) + intent { copy(isLoading = false) } }, ) } From 7a2867eb624489c8610b1d6dc80ba37b7a3df840 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 20:45:55 +0900 Subject: [PATCH 24/60] =?UTF-8?q?fix:=20api=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=ED=8B=80=EB=A6=B0=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/model/SnsProviders.kt | 2 +- data/src/main/java/com/susu/data/network/ApiService.kt | 2 +- .../java/com/susu/data/repository/AuthRepositoryImpl.kt | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/com/susu/data/model/SnsProviders.kt b/data/src/main/java/com/susu/data/model/SnsProviders.kt index 82968641..b487b902 100644 --- a/data/src/main/java/com/susu/data/model/SnsProviders.kt +++ b/data/src/main/java/com/susu/data/model/SnsProviders.kt @@ -1,5 +1,5 @@ package com.susu.data.model -enum class SnsProviders(name: String) { +enum class SnsProviders(val path: String) { Kakao("KAKAO") } diff --git a/data/src/main/java/com/susu/data/network/ApiService.kt b/data/src/main/java/com/susu/data/network/ApiService.kt index 49114f08..51ff1236 100644 --- a/data/src/main/java/com/susu/data/network/ApiService.kt +++ b/data/src/main/java/com/susu/data/network/ApiService.kt @@ -27,7 +27,7 @@ interface ApiService { @GET("oauth/{provider}/sign-up/valid") suspend fun checkValidRegister( @Path("provider") provider: String, - @Body accessTokenRequest: AccessTokenRequest, + @Query("accessToken") accessToken: String, ): ValidRegisterResponse @POST("auth/logout") diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 68d0cb77..26f53e3d 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -16,7 +16,7 @@ class AuthRepositoryImpl @Inject constructor( accessToken: String, ) = kotlin.runCatching { apiService.login( - provider = SnsProviders.Kakao.name, + provider = SnsProviders.Kakao.path, accessTokenRequest = AccessTokenRequest(accessToken), ).toDomain() } @@ -26,7 +26,7 @@ class AuthRepositoryImpl @Inject constructor( user: User, ) = runCatching { apiService.signUp( - provider = SnsProviders.Kakao.name, + provider = SnsProviders.Kakao.path, accessToken = snsAccessToken, user = user.toData(), ).toDomain() @@ -36,8 +36,8 @@ class AuthRepositoryImpl @Inject constructor( accessToken: String, ): Boolean { return apiService.checkValidRegister( - provider = SnsProviders.Kakao.name, - accessTokenRequest = AccessTokenRequest(accessToken), + provider = SnsProviders.Kakao.path, + accessToken = accessToken, ).canRegister } From 5bcd5dda06663d24e7334dc52d32b08a5340d31f Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Fri, 22 Dec 2023 20:46:51 +0900 Subject: [PATCH 25/60] =?UTF-8?q?fix:=20ActivityContext=EA=B0=80=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=EC=9D=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/domain/util/KakaoLoginProvider.kt | 24 -------- .../com/susu/domain/util/KakaoSdkProvider.kt | 8 +++ ...ginProviderImpl.kt => KakaoLoginHelper.kt} | 41 ++----------- .../loginsignup/KakaoSdkProviderImpl.kt | 34 +++++++++++ .../feature/loginsignup/di/KakaoModule.kt | 9 +-- .../feature/loginsignup/login/LoginScreen.kt | 16 ++++- .../loginsignup/login/LoginViewModel.kt | 60 +++++++++---------- 7 files changed, 94 insertions(+), 98 deletions(-) delete mode 100644 domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt create mode 100644 domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt rename feature/loginsignup/src/main/java/com/susu/feature/loginsignup/{KakaoLoginProviderImpl.kt => KakaoLoginHelper.kt} (66%) create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt diff --git a/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt deleted file mode 100644 index 0e99542e..00000000 --- a/domain/src/main/java/com/susu/domain/util/KakaoLoginProvider.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.susu.domain.util - -interface KakaoLoginProvider { - fun hasKakaoLoginHistory(): Boolean - fun getAccessToken(): String? - - fun login( - onSuccess: (String) -> Unit, - onFailed: (Throwable?) -> Unit, - ) - - fun loginWithKakaoTalk( - onSuccess: (String) -> Unit, - onFailed: (Throwable?) -> Unit, - ) - - fun loginWithKakaoAccount( - onSuccess: (String) -> Unit, - onFailed: (Throwable?) -> Unit, - ) - - fun logout(): Result - fun unlink(): Result -} diff --git a/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt new file mode 100644 index 00000000..c63ddfea --- /dev/null +++ b/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt @@ -0,0 +1,8 @@ +package com.susu.domain.util + +interface KakaoSdkProvider { + fun hasKakaoLoginHistory(): Boolean + fun getAccessToken(): String? + fun logout(): Result + fun unlink(): Result +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt similarity index 66% rename from feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt rename to feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt index 47807671..5d6a50af 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginProviderImpl.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt @@ -1,30 +1,17 @@ package com.susu.feature.loginsignup import android.content.Context -import com.kakao.sdk.auth.AuthApiClient -import com.kakao.sdk.auth.TokenManagerProvider import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient -import com.susu.domain.util.KakaoLoginProvider -import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber -import javax.inject.Inject -class KakaoLoginProviderImpl @Inject constructor( - @ApplicationContext val context: Context, -) : KakaoLoginProvider { +class KakaoLoginHelper( + private val context: Context +) { - override fun hasKakaoLoginHistory(): Boolean { - return AuthApiClient.instance.hasToken() - } - - override fun getAccessToken(): String? { - return TokenManagerProvider.instance.manager.getToken()?.accessToken - } - - override fun login( + fun login( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -51,7 +38,7 @@ class KakaoLoginProviderImpl @Inject constructor( } } - override fun loginWithKakaoTalk( + private fun loginWithKakaoTalk( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -65,7 +52,7 @@ class KakaoLoginProviderImpl @Inject constructor( } } - override fun loginWithKakaoAccount( + private fun loginWithKakaoAccount( onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -77,20 +64,4 @@ class KakaoLoginProviderImpl @Inject constructor( } } } - - override fun logout() = runCatching { - UserApiClient.instance.logout { error -> - if (error != null) { - throw error - } - } - } - - override fun unlink() = runCatching { - UserApiClient.instance.unlink { error -> - if (error != null) { - throw error - } - } - } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt new file mode 100644 index 00000000..33972153 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt @@ -0,0 +1,34 @@ +package com.susu.feature.loginsignup + +import com.kakao.sdk.auth.AuthApiClient +import com.kakao.sdk.auth.TokenManagerProvider +import com.kakao.sdk.user.UserApiClient +import com.susu.domain.util.KakaoSdkProvider +import javax.inject.Inject + +class KakaoSdkProviderImpl @Inject constructor() : KakaoSdkProvider { + + override fun hasKakaoLoginHistory(): Boolean { + return AuthApiClient.instance.hasToken() + } + + override fun getAccessToken(): String? { + return TokenManagerProvider.instance.manager.getToken()?.accessToken + } + + override fun logout() = runCatching { + UserApiClient.instance.logout { error -> + if (error != null) { + throw error + } + } + } + + override fun unlink() = runCatching { + UserApiClient.instance.unlink { error -> + if (error != null) { + throw error + } + } + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt index 761f6b01..2a21e034 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt @@ -1,7 +1,7 @@ package com.susu.feature.loginsignup.di -import com.susu.domain.util.KakaoLoginProvider -import com.susu.feature.loginsignup.KakaoLoginProviderImpl +import com.susu.domain.util.KakaoSdkProvider +import com.susu.feature.loginsignup.KakaoSdkProviderImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -15,6 +15,7 @@ abstract class KakaoModule { @Binds @Singleton abstract fun bindsKakaoLoginProvider( - kakaoLoginProviderImpl: KakaoLoginProviderImpl - ): KakaoLoginProvider + kakaoLoginProviderImpl: KakaoSdkProviderImpl + ): KakaoSdkProvider } + diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index 3c7bf50e..7ea93980 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -13,15 +13,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.hilt.navigation.compose.hiltViewModel +import com.susu.feature.loginsignup.KakaoLoginHelper import com.susu.feature.loginsignup.R @Composable fun LoginScreen( viewModel: LoginViewModel = hiltViewModel(), + kakaoLoginHelper: KakaoLoginHelper, ) { val context = LocalContext.current val uiState by viewModel.uiState.collectAsState() @@ -45,16 +48,23 @@ fun LoginScreen( } Box( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { if (uiState.showVote) { SplashVote(onClick = viewModel::selectSignUpVote) } else { - KakaoLogin(onClick = viewModel::login) + KakaoLogin( + onClick = { + kakaoLoginHelper.login( + onSuccess = { viewModel.login(it) }, + onFailed = { viewModel.showToast(it) }, + ) + }, + ) } if (uiState.isLoading) { - CircularProgressIndicator() + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index 76603754..5432e1c2 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -4,15 +4,16 @@ import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository -import com.susu.domain.util.KakaoLoginProvider +import com.susu.domain.util.KakaoSdkProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val kakaoLoginProvider: KakaoLoginProvider, + private val kakaoSdkProvider: KakaoSdkProvider, private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, ) : BaseViewModel(LoginContract.LoginState()) { @@ -22,14 +23,14 @@ class LoginViewModel @Inject constructor( intent { copy(isLoading = true) } Timber.tag("AUTH").d("카카오 로그인 이력 확인") // 1. 과거 카톡 로그인 이력 확인 - if (!kakaoLoginProvider.hasKakaoLoginHistory()) { + if (!kakaoSdkProvider.hasKakaoLoginHistory()) { // 1-1. 신규 유저 intent { copy(isLoading = false, showVote = true) } Timber.tag("AUTH").d("신규유져") } else { // 2. 카카오 access token 존재 시 로그인 시도 Timber.d("수수 로그인 시도") - kakaoLoginProvider.getAccessToken()?.let { + kakaoSdkProvider.getAccessToken()?.let { authRepository.login(it).onSuccess { Timber.tag("AUTH").d("수수 로그인 성공") postSideEffect(LoginContract.LoginEffect.NavigateToReceived) @@ -41,39 +42,34 @@ class LoginViewModel @Inject constructor( } } - fun login() { - Timber.tag("AUTH").d("카카오 로그인 시도") - kakaoLoginProvider.login( - onSuccess = { accessToken -> - Timber.tag("AUTH").d("카카오 로그인 성공") - viewModelScope.launch { - intent { copy(isLoading = true) } - // 수수 서버에 가입되지 않은 회원이라면 -> 회원 정보 기입 후 수수 회원가입 - if (authRepository.canRegister(accessToken)) { - Timber.tag("AUTH").d("수수 가입 가능") - postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) - } else { - authRepository.login(accessToken).onSuccess { token -> - Timber.tag("AUTH").d("수수 로그인 성공") - tokenRepository.saveTokens(token) - postSideEffect(LoginContract.LoginEffect.NavigateToReceived) - }.onFailure { - Timber.tag("AUTH").d("수수 로그인 실패 ${it.message}") - postSideEffect(LoginContract.LoginEffect.ShowToast(it.message ?: "수수 로그인 실패")) - } + fun login(accessToken: String) { + viewModelScope.launch { + intent { copy(isLoading = true) } + // 수수 서버에 가입되지 않은 회원이라면 -> 회원 정보 기입 후 수수 회원가입 + if (authRepository.canRegister(accessToken)) { + Timber.tag("AUTH").d("수수 가입 가능") + postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) + } else { + authRepository.login(accessToken).onSuccess { token -> + Timber.tag("AUTH").d("수수 로그인 성공") + runBlocking { + tokenRepository.saveTokens(token) } - intent { copy(isLoading = false) } + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + }.onFailure { + Timber.tag("AUTH").d("수수 로그인 실패 ${it.message}") + postSideEffect(LoginContract.LoginEffect.ShowToast(it.message ?: "수수 로그인 실패")) } - }, - onFailed = { - Timber.tag("AUTH").d("카카오 로그인 실패 ${it?.message}") - postSideEffect(LoginContract.LoginEffect.ShowToast(it?.message ?: "카카오 로그인 실패")) - intent { copy(isLoading = false) } - }, - ) + } + intent { copy(isLoading = false) } + } } fun selectSignUpVote() { intent { copy(showVote = false) } } + + fun showToast(error: Throwable?) { + postSideEffect(LoginContract.LoginEffect.ShowToast(error?.message ?: "에러")) + } } From cebb823be01a01a93a35473a33090c6bbc840580 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 12:48:50 +0900 Subject: [PATCH 26/60] =?UTF-8?q?fix:=20token=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EC=9D=84=20=EC=99=84=EB=A3=8C=ED=95=A0=20=EB=95=8C=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EB=8C=80=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/susu/feature/loginsignup/login/LoginViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index 5432e1c2..76ea4783 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -31,8 +31,11 @@ class LoginViewModel @Inject constructor( // 2. 카카오 access token 존재 시 로그인 시도 Timber.d("수수 로그인 시도") kakaoSdkProvider.getAccessToken()?.let { - authRepository.login(it).onSuccess { + authRepository.login(it).onSuccess { token -> Timber.tag("AUTH").d("수수 로그인 성공") + runBlocking { + tokenRepository.saveTokens(token) + } postSideEffect(LoginContract.LoginEffect.NavigateToReceived) } intent { copy(isLoading = false) } From 7f09a876937b14b5c8264e16b8a16712dff6d2b5 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 13:53:23 +0900 Subject: [PATCH 27/60] =?UTF-8?q?refactor:=20encrpted-datastore=EB=A1=9C?= =?UTF-8?q?=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/DataStoreModule.kt | 27 +++++++++++++ .../data/repository/TokenRepositoryImpl.kt | 40 +++++-------------- .../susu/data/security/SecurityPreferences.kt | 31 ++++++++++++++ .../susu/domain/repository/TokenRepository.kt | 4 +- 4 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 data/src/main/java/com/susu/data/security/SecurityPreferences.kt diff --git a/data/src/main/java/com/susu/data/di/DataStoreModule.kt b/data/src/main/java/com/susu/data/di/DataStoreModule.kt index 87050e75..10dfcc0a 100644 --- a/data/src/main/java/com/susu/data/di/DataStoreModule.kt +++ b/data/src/main/java/com/susu/data/di/DataStoreModule.kt @@ -7,11 +7,15 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile +import com.susu.data.security.SecurityPreferences +import com.susu.data.security.generateSecurityPreferences import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import tech.thdev.useful.encrypted.data.store.preferences.security.generateUsefulSecurity +import javax.inject.Qualifier import javax.inject.Singleton @InstallIn(SingletonComponent::class) @@ -29,5 +33,28 @@ object DataStoreModule { ) } + @EncryptedDataStore + @Singleton + @Provides + fun providedEncryptedDataStore(@ApplicationContext context: Context): DataStore { + return PreferenceDataStoreFactory.create( + corruptionHandler = ReplaceFileCorruptionHandler( + produceNewData = { emptyPreferences() }, + ), + produceFile = { context.preferencesDataStoreFile(ENCRYPTED_DATASTORE_NAME) }, + ) + } + + @Singleton + @Provides + fun provideSecurityPreference( + @EncryptedDataStore dataStore: DataStore, + ): SecurityPreferences = dataStore.generateSecurityPreferences(generateUsefulSecurity()) + private const val DATASTORE_NAME = "susu-datastore" + private const val ENCRYPTED_DATASTORE_NAME = "susu-datastore-encrypted" } + +@Qualifier +@Retention(AnnotationRetention.BINARY) +internal annotation class EncryptedDataStore diff --git a/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt index db2b1da1..302225a0 100644 --- a/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/TokenRepositoryImpl.kt @@ -1,51 +1,31 @@ package com.susu.data.repository -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey import com.susu.core.model.Token -import com.susu.data.extension.secureEdit -import com.susu.data.extension.secureMap +import com.susu.data.security.SecurityPreferences import com.susu.domain.repository.TokenRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject class TokenRepositoryImpl @Inject constructor( - private val dataStore: DataStore, + private val securityPreferences: SecurityPreferences, ) : TokenRepository { - override fun getAccessToken(): Flow { - return dataStore.data.secureMap { preference -> - preference[ACCESS_TOKEN] - } + override fun getAccessToken(): Flow { + return securityPreferences.flowAccessToken() } - override fun getRefreshToken(): Flow { - return dataStore.data.secureMap { preference -> - preference[REFRESH_TOKEN] - } + override fun getRefreshToken(): Flow { + return securityPreferences.flowRefreshToken() } override suspend fun saveTokens(token: Token) { - dataStore.secureEdit(token.accessToken) { preference, encrypted -> - println(encrypted) - preference[ACCESS_TOKEN] = encrypted - } - dataStore.secureEdit(token.refreshToken) { preference, encrypted -> - preference[REFRESH_TOKEN] = encrypted + securityPreferences.run { + setAccessToken(token.accessToken) + setRefreshToken(token.refreshToken) } } override suspend fun deleteTokens() { - dataStore.edit { preference -> - preference.remove(ACCESS_TOKEN) - preference.remove(REFRESH_TOKEN) - } - } - - companion object { - private val ACCESS_TOKEN = stringPreferencesKey("accessToken") - private val REFRESH_TOKEN = stringPreferencesKey("refreshToken") + securityPreferences.clearAll() } } diff --git a/data/src/main/java/com/susu/data/security/SecurityPreferences.kt b/data/src/main/java/com/susu/data/security/SecurityPreferences.kt new file mode 100644 index 00000000..804b93ec --- /dev/null +++ b/data/src/main/java/com/susu/data/security/SecurityPreferences.kt @@ -0,0 +1,31 @@ +package com.susu.data.security + +import kotlinx.coroutines.flow.Flow +import tech.thdev.useful.encrypted.data.store.preferences.ksp.annotations.UsefulPreferences +import tech.thdev.useful.encrypted.data.store.preferences.ksp.annotations.value.ClearValues +import tech.thdev.useful.encrypted.data.store.preferences.ksp.annotations.value.GetValue +import tech.thdev.useful.encrypted.data.store.preferences.ksp.annotations.value.SetValue + +@UsefulPreferences +interface SecurityPreferences { + + @GetValue(KEY_ACCESS_TOKEN, defaultValue = "") + fun flowAccessToken(): Flow + + @SetValue(KEY_ACCESS_TOKEN) + suspend fun setAccessToken(value: String) + + @GetValue(KEY_REFRESH_TOKEN, defaultValue = "") + fun flowRefreshToken(): Flow + + @SetValue(KEY_REFRESH_TOKEN) + suspend fun setRefreshToken(value: String) + + @ClearValues + suspend fun clearAll() + + companion object { + private const val KEY_ACCESS_TOKEN = "key-access-token" + private const val KEY_REFRESH_TOKEN = "key-refresh-token" + } +} diff --git a/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt b/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt index 7fd8fb51..1320100a 100644 --- a/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/TokenRepository.kt @@ -4,8 +4,8 @@ import com.susu.core.model.Token import kotlinx.coroutines.flow.Flow interface TokenRepository { - fun getAccessToken(): Flow - fun getRefreshToken(): Flow + fun getAccessToken(): Flow + fun getRefreshToken(): Flow suspend fun saveTokens(token: Token) suspend fun deleteTokens() } From 2cc691a1ebfd90af401f9934c16cfbc0e10a424e Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 14:36:12 +0900 Subject: [PATCH 28/60] =?UTF-8?q?fix:=20kakao=20token=20=EB=A7=8C=EB=A3=8C?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=ED=9B=84=20=EC=88=98=EC=88=98=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=EB=8F=84=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/susu/domain/util/KakaoSdkProvider.kt | 2 +- .../loginsignup/KakaoSdkProviderImpl.kt | 13 +++++++++-- .../loginsignup/login/LoginViewModel.kt | 23 +++++++++++-------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt index c63ddfea..54dd10fe 100644 --- a/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt +++ b/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt @@ -2,7 +2,7 @@ package com.susu.domain.util interface KakaoSdkProvider { fun hasKakaoLoginHistory(): Boolean - fun getAccessToken(): String? + fun getAccessToken(callback: (String?) -> Unit) fun logout(): Result fun unlink(): Result } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt index 33972153..8c682938 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt @@ -12,8 +12,17 @@ class KakaoSdkProviderImpl @Inject constructor() : KakaoSdkProvider { return AuthApiClient.instance.hasToken() } - override fun getAccessToken(): String? { - return TokenManagerProvider.instance.manager.getToken()?.accessToken + override fun getAccessToken( + callback: (String?) -> Unit, + ) { + UserApiClient.instance.accessTokenInfo { _, error -> + if (error != null) { + callback(null) + } else { + // 토큰 유효성 체크 성공(필요 시 토큰 갱신됨) + callback(TokenManagerProvider.instance.manager.getToken()?.accessToken) + } + } } override fun logout() = runCatching { diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index 76ea4783..c82bf6e4 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -22,25 +22,28 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { intent { copy(isLoading = true) } Timber.tag("AUTH").d("카카오 로그인 이력 확인") - // 1. 과거 카톡 로그인 이력 확인 + // 1. 카카오 토큰 존재 여부 if (!kakaoSdkProvider.hasKakaoLoginHistory()) { // 1-1. 신규 유저 intent { copy(isLoading = false, showVote = true) } Timber.tag("AUTH").d("신규유져") } else { // 2. 카카오 access token 존재 시 로그인 시도 - Timber.d("수수 로그인 시도") - kakaoSdkProvider.getAccessToken()?.let { - authRepository.login(it).onSuccess { token -> - Timber.tag("AUTH").d("수수 로그인 성공") - runBlocking { - tokenRepository.saveTokens(token) + Timber.tag("AUTH").d("수수 로그인 시도") + kakaoSdkProvider.getAccessToken { + it?.let { accessToken -> + viewModelScope.launch { + authRepository.login(accessToken).onSuccess { token -> + Timber.tag("AUTH").d("수수 로그인 성공") + runBlocking { + tokenRepository.saveTokens(token) + } + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + } + intent { copy(isLoading = false) } } - postSideEffect(LoginContract.LoginEffect.NavigateToReceived) } - intent { copy(isLoading = false) } } - // 3. 카카오 로그인 필요 } } } From 5d533b91a08385105fb53cda4a8c2f4bb64347de Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:34:14 +0900 Subject: [PATCH 29/60] =?UTF-8?q?feat:=20ApiService=EC=97=90=20=EC=88=98?= =?UTF-8?q?=EC=88=98=20=ED=9A=8C=EC=9B=90=20=ED=83=88=ED=87=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/network/ApiService.kt | 6 +++++- .../java/com/susu/data/repository/AuthRepositoryImpl.kt | 4 ++++ .../main/java/com/susu/domain/repository/AuthRepository.kt | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/com/susu/data/network/ApiService.kt b/data/src/main/java/com/susu/data/network/ApiService.kt index 51ff1236..8d6fdc5e 100644 --- a/data/src/main/java/com/susu/data/network/ApiService.kt +++ b/data/src/main/java/com/susu/data/network/ApiService.kt @@ -4,6 +4,7 @@ import com.susu.data.model.TokenEntity import com.susu.data.model.UserEntity import com.susu.data.model.request.AccessTokenRequest import com.susu.data.model.response.ValidRegisterResponse +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -31,5 +32,8 @@ interface ApiService { ): ValidRegisterResponse @POST("auth/logout") - suspend fun logout() + suspend fun logout(): Response + + @POST("auth/withdraw") + suspend fun withdraw(): Response } diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 26f53e3d..2bc307e5 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -44,4 +44,8 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun logout() { apiService.logout() } + + override suspend fun withdraw() { + apiService.withdraw() + } } diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt index 18dd114d..d7ec259a 100644 --- a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt @@ -8,4 +8,5 @@ interface AuthRepository { suspend fun signUp(snsAccessToken: String, user: User): Result suspend fun canRegister(accessToken: String): Boolean suspend fun logout() + suspend fun withdraw() } From ca2e3f593aba66eed006cf02cb026ccbee278183 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:35:09 +0900 Subject: [PATCH 30/60] =?UTF-8?q?feat:=20login,=20signup=20nested=20naviga?= =?UTF-8?q?tion=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/loginsignup/login/LoginScreen.kt | 17 ++++++--- .../navigation/LoginSignupNavigation.kt | 36 +++++++++++++++---- .../com/susu/feature/navigator/MainScreen.kt | 2 +- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index 7ea93980..e7eb2992 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator @@ -17,16 +18,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.susu.core.ui.extension.susuClickable import com.susu.feature.loginsignup.KakaoLoginHelper import com.susu.feature.loginsignup.R @Composable fun LoginScreen( viewModel: LoginViewModel = hiltViewModel(), - kakaoLoginHelper: KakaoLoginHelper, + navigateToReceived: () -> Unit, + navigateToSignUp: () -> Unit, ) { val context = LocalContext.current + val kakaoLoginHelper = KakaoLoginHelper(context) val uiState by viewModel.uiState.collectAsState() LaunchedEffect(key1 = viewModel.sideEffect) { @@ -38,10 +43,12 @@ fun LoginScreen( LoginContract.LoginEffect.NavigateToReceived -> { Toast.makeText(context, "기존 회원 로그인 성공", Toast.LENGTH_SHORT).show() + navigateToReceived() } LoginContract.LoginEffect.NavigateToSignUp -> { Toast.makeText(context, "신규 회원 가입 창으로", Toast.LENGTH_SHORT).show() + navigateToSignUp() } } } @@ -81,7 +88,9 @@ fun SplashVote(onClick: () -> Unit) { @Composable fun KakaoLogin(onClick: () -> Unit) { - Button(onClick = onClick, modifier = Modifier.wrapContentHeight().fillMaxWidth()) { - Image(painter = painterResource(id = R.drawable.btn_kakao_login), contentDescription = null) - } + Image( + modifier = Modifier.height(80.dp).fillMaxWidth().susuClickable { onClick() }, + painter = painterResource(id = R.drawable.btn_kakao_login), + contentDescription = null, + ) } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt index 236b6b11..f2139363 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt @@ -4,19 +4,41 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.susu.feature.loginsignup.LoginSignupScreen +import androidx.navigation.navigation +import com.susu.feature.loginsignup.test.TestScreen +import com.susu.feature.loginsignup.login.LoginScreen +import com.susu.feature.loginsignup.signup.SignUpScreen @Suppress("unused") fun NavController.navigateLoginSignup(navOptions: NavOptions) { - navigate(LoginSignupRoute.route, navOptions) + navigate(LoginSignupRoute.Parent.route, navOptions) } -fun NavGraphBuilder.loginSignupNavGraph() { - composable(route = LoginSignupRoute.route) { - LoginSignupScreen() +fun NavGraphBuilder.loginSignupNavGraph(navController: NavController) { + navigation(startDestination = LoginSignupRoute.Parent.Login.route, route = LoginSignupRoute.Parent.route) { + composable(route = LoginSignupRoute.Parent.Login.route) { + LoginScreen( + navigateToReceived = { navController.navigate(LoginSignupRoute.Parent.Test.route) }, + navigateToSignUp = { navController.navigate(LoginSignupRoute.Parent.SignUp.route) }, + ) + } + composable(route = LoginSignupRoute.Parent.SignUp.route) { + SignUpScreen( + navigateToReceived = { navController.navigate(LoginSignupRoute.Parent.Test.route) }, + ) + } + composable(route = LoginSignupRoute.Parent.Test.route) { + TestScreen( + navigateToLogin = { navController.popBackStack() }, + ) + } } } -object LoginSignupRoute { - const val route = "login-signup" +sealed class LoginSignupRoute(val route: String) { + data object Parent : LoginSignupRoute("login-signup") { + data object Login : LoginSignupRoute("login") + data object SignUp : LoginSignupRoute("signup") + data object Test : LoginSignupRoute("test") + } } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt index d11c1efe..6a7fa5bb 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt @@ -36,7 +36,7 @@ internal fun MainScreen( navController = navigator.navController, startDestination = navigator.startDestination, ) { - loginSignupNavGraph() + loginSignupNavGraph(navigator.navController) sentNavGraph( padding = innerPadding, From 286ac14a64dfc02ddded451e7c551130d37551d4 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:36:29 +0900 Subject: [PATCH 31/60] =?UTF-8?q?fix:=20=EC=88=98=EC=88=98=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=ED=97=A4=EB=8D=94=20=ED=98=95=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/network/TokenAuthenticator.kt | 2 +- data/src/main/java/com/susu/data/network/TokenInterceptor.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt index 3be9038c..352ed7e1 100644 --- a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt +++ b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt @@ -36,7 +36,7 @@ class TokenAuthenticator @Inject constructor( } else { // 3. 헤더에 토큰을 교체한 request 생성 response.request.newBuilder() - .header("Authorization", "Bearer ${tokenResponse.body()!!.accessToken}") + .header("X-SUSU-AUTH-TOKEN", tokenResponse.body()!!.accessToken) .build() } } diff --git a/data/src/main/java/com/susu/data/network/TokenInterceptor.kt b/data/src/main/java/com/susu/data/network/TokenInterceptor.kt index b309d98d..901c50b6 100644 --- a/data/src/main/java/com/susu/data/network/TokenInterceptor.kt +++ b/data/src/main/java/com/susu/data/network/TokenInterceptor.kt @@ -15,7 +15,7 @@ class TokenInterceptor @Inject constructor( tokenRepository.getAccessToken().firstOrNull() } val request = chain.request().newBuilder().apply { - addHeader("Authorization", "Bearer $token") + addHeader("X-SUSU-AUTH-TOKEN", token ?: "") } return chain.proceed(request.build()) } From 2c8aad0c235db51a73b7b95295b6424e6211efd8 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:37:13 +0900 Subject: [PATCH 32/60] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20UI=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/loginsignup/KakaoLoginHelper.kt | 2 +- .../feature/loginsignup/LoginSignupScreen.kt | 21 ------- .../loginsignup/login/LoginViewModel.kt | 2 +- .../loginsignup/signup/SignUpContract.kt | 17 ++++++ .../loginsignup/signup/SignUpScreen.kt | 56 +++++++++++++++++++ .../loginsignup/signup/SignUpViewModel.kt | 54 ++++++++++++++++++ .../feature/loginsignup/test/TestContract.kt | 12 ++++ .../feature/loginsignup/test/TestScreen.kt | 33 +++++++++++ .../feature/loginsignup/test/TestViewModel.kt | 38 +++++++++++++ .../susu/feature/navigator/MainNavigator.kt | 2 +- 10 files changed, 213 insertions(+), 24 deletions(-) delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/LoginSignupScreen.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt index 5d6a50af..48c35629 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt @@ -8,7 +8,7 @@ import com.kakao.sdk.user.UserApiClient import timber.log.Timber class KakaoLoginHelper( - private val context: Context + private val context: Context, ) { fun login( diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/LoginSignupScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/LoginSignupScreen.kt deleted file mode 100644 index 08a41d85..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/LoginSignupScreen.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.susu.feature.loginsignup - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import com.susu.core.designsystem.theme.SusuTheme - -@Composable -fun LoginSignupScreen() { - Text( - text = "로그인 회원가입", - ) -} - -@Preview -@Composable -fun SentScreenPreview() { - SusuTheme { - LoginSignupScreen() - } -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index c82bf6e4..185e79eb 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -57,7 +57,7 @@ class LoginViewModel @Inject constructor( postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) } else { authRepository.login(accessToken).onSuccess { token -> - Timber.tag("AUTH").d("수수 로그인 성공") + Timber.tag("AUTH").d("수수 로그인 성공 ${token.accessToken}") runBlocking { tokenRepository.saveTokens(token) } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt new file mode 100644 index 00000000..964aeb1c --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt @@ -0,0 +1,17 @@ +package com.susu.feature.loginsignup.signup + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +class SignUpContract { + sealed class SignUpEffect : SideEffect { + data object NavigateToReceived : SignUpEffect() + data class ShowToast(val msg: String) : SignUpEffect() + } + + data class SignUpState( + val name: String = "", + val gender: String = "M", + val birth: String = "0", + ) : UiState +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt new file mode 100644 index 00000000..71b3b892 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt @@ -0,0 +1,56 @@ +package com.susu.feature.loginsignup.signup + +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import androidx.hilt.navigation.compose.hiltViewModel + +@Composable +fun SignUpScreen( + viewModel: SignUpViewModel = hiltViewModel(), + navigateToReceived: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsState() + val context = LocalContext.current + + LaunchedEffect(key1 = viewModel.sideEffect) { + viewModel.sideEffect.collect { sideEffect -> + when (sideEffect) { + SignUpContract.SignUpEffect.NavigateToReceived -> { + Toast.makeText(context, "가입 성공", Toast.LENGTH_SHORT).show() + navigateToReceived() + } + + is SignUpContract.SignUpEffect.ShowToast -> { + Toast.makeText(context, sideEffect.msg, Toast.LENGTH_SHORT).show() + } + } + } + } + + Column { + TextField(value = uiState.name, onValueChange = viewModel::updateName, label = { Text(text = "이름") }) + TextField( + value = uiState.gender, onValueChange = viewModel::updateGender, + label = { + Text(text = "성별 (M/F)") + }, + ) + TextField( + value = uiState.birth, + onValueChange = { viewModel.updateBirth(it) }, + label = { Text(text = "출생년도 (1930 ~ 2030)") }, + + ) + Button(onClick = viewModel::signUp) { + Text(text = "회원가입") + } + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt new file mode 100644 index 00000000..05c4e2dd --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt @@ -0,0 +1,54 @@ +package com.susu.feature.loginsignup.signup + +import androidx.lifecycle.viewModelScope +import com.susu.core.model.User +import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.repository.AuthRepository +import com.susu.domain.repository.TokenRepository +import com.susu.domain.util.KakaoSdkProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import javax.inject.Inject + +@HiltViewModel +class SignUpViewModel @Inject constructor( + private val kakaoSdkProvider: KakaoSdkProvider, + private val authRepository: AuthRepository, + private val tokenRepository: TokenRepository, +) : BaseViewModel(SignUpContract.SignUpState()) { + + fun updateName(name: String) { + intent { copy(name = name) } + } + + fun updateGender(gender: String) { + intent { copy(gender = gender) } + } + + fun updateBirth(birth: String) { + intent { copy(birth = birth) } + } + + fun signUp() { + kakaoSdkProvider.getAccessToken { + viewModelScope.launch { + if (it != null) { + authRepository.signUp( + snsAccessToken = it, + user = User( + name = uiState.value.name, + gender = uiState.value.gender, + birth = uiState.value.birth.toInt(), + ), + ).onSuccess { + runBlocking { tokenRepository.saveTokens(it) } + postSideEffect(SignUpContract.SignUpEffect.NavigateToReceived) + }.onFailure { + postSideEffect(SignUpContract.SignUpEffect.ShowToast(it.message ?: "에러 발생")) + } + } + } + } + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt new file mode 100644 index 00000000..80cc5677 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt @@ -0,0 +1,12 @@ +package com.susu.feature.loginsignup.test + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +class TestContract { + sealed class TestEffect : SideEffect { + data object NavigateToLogin : TestEffect() + } + + object TestState : UiState +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt new file mode 100644 index 00000000..cbda00b6 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt @@ -0,0 +1,33 @@ +package com.susu.feature.loginsignup.test + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.hilt.navigation.compose.hiltViewModel + +@Composable +fun TestScreen( + viewModel: TestViewModel = hiltViewModel(), + navigateToLogin: () -> Unit, +) { + LaunchedEffect(key1 = viewModel.sideEffect) { + viewModel.sideEffect.collect { sideEffect -> + when (sideEffect) { + TestContract.TestEffect.NavigateToLogin -> navigateToLogin() + } + } + } + + Column { + Button(onClick = viewModel::logout) { + Text(text = "로그아웃") + } + Button( + onClick = viewModel::withdraw, + ) { + Text(text = "탈퇴") + } + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt new file mode 100644 index 00000000..ffaa83f0 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt @@ -0,0 +1,38 @@ +package com.susu.feature.loginsignup.test + +import androidx.lifecycle.viewModelScope +import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.repository.AuthRepository +import com.susu.domain.repository.TokenRepository +import com.susu.domain.util.KakaoSdkProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import javax.inject.Inject + +// 마이페이지에 들어갈 기능입니다. +@HiltViewModel +class TestViewModel @Inject constructor( + private val authRepository: AuthRepository, + private val tokenRepository: TokenRepository, + private val kakaoSdkProvider: KakaoSdkProvider, +) : BaseViewModel(TestContract.TestState) { + fun logout() { + viewModelScope.launch { + authRepository.logout() + kakaoSdkProvider.logout() + tokenRepository.deleteTokens() + } + postSideEffect(TestContract.TestEffect.NavigateToLogin) + } + + fun withdraw() { + kakaoSdkProvider.unlink().onSuccess { + viewModelScope.launch { + runBlocking { authRepository.withdraw() } + tokenRepository.deleteTokens() + } + } + postSideEffect(TestContract.TestEffect.NavigateToLogin) + } +} diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt index 9425577b..d7a54282 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt @@ -23,7 +23,7 @@ internal class MainNavigator( @Composable get() = navController .currentBackStackEntryAsState().value?.destination - val startDestination = LoginSignupRoute.route + val startDestination = LoginSignupRoute.Parent.route val currentTab: MainNavigationTab? @Composable get() = currentDestination From 04d9e017bb68308e81c05382d0c54481e850958a Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:41:11 +0900 Subject: [PATCH 33/60] =?UTF-8?q?chore:=20hilt-navigation-compose=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../convention/src/main/java/FeatureComposeConventionPlugin.kt | 2 -- gradle/libs.versions.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt b/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt index 16d02087..7d0374b0 100644 --- a/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/java/FeatureComposeConventionPlugin.kt @@ -26,8 +26,6 @@ internal class FeatureComposeConventionPlugin : Plugin { "implementation"(libs.findLibrary("kotlinx.coroutines.android").get()) "implementation"(libs.findLibrary("kotlinx.coroutines.core").get()) - "implementation"(libs.findLibrary("hilt.navigation.compose").get()) - "androidTestImplementation"(libs.findLibrary("junit").get()) "implementation"(libs.findLibrary("timber").get()) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 85dac190..746c65a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -187,5 +187,5 @@ firebase = ["firebase-analytics"] androidx-lifecycle = ["androidx-lifecycle-runtime-ktx", "androidx-lifecycle-viewmodel-ktx"] androidx-navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"] coroutine = ["kotlinx-coroutines-android", "kotlinx-coroutines-core", "kotlinx-coroutines-test"] -compose = ["ui", "ui-graphics", "ui-tooling-preview", "material3-compose", "coil-compose", "ui-foundation", "activity-compose", "lifecycle-compose", "navigation-compose"] +compose = ["ui", "ui-graphics", "ui-tooling-preview", "material3-compose", "coil-compose", "ui-foundation", "activity-compose", "lifecycle-compose", "navigation-compose", "hilt-navigation-compose"] compose-debug = ["ui-tooling", "ui-test-manifest" ] From 1a7748daeb20eb9e5cfa83fca4107170f867fc52 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:42:34 +0900 Subject: [PATCH 34/60] =?UTF-8?q?chore:=20kakao=20sdk=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20social=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/feature/loginsignup/di/KakaoModule.kt | 2 +- .../main/java/com/susu/feature/loginsignup/login/LoginScreen.kt | 2 +- .../susu/feature/loginsignup/{ => social}/KakaoLoginHelper.kt | 2 +- .../feature/loginsignup/{ => social}/KakaoSdkProviderImpl.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename feature/loginsignup/src/main/java/com/susu/feature/loginsignup/{ => social}/KakaoLoginHelper.kt (98%) rename feature/loginsignup/src/main/java/com/susu/feature/loginsignup/{ => social}/KakaoSdkProviderImpl.kt (96%) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt index 2a21e034..421ed760 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt @@ -1,7 +1,7 @@ package com.susu.feature.loginsignup.di import com.susu.domain.util.KakaoSdkProvider -import com.susu.feature.loginsignup.KakaoSdkProviderImpl +import com.susu.feature.loginsignup.social.KakaoSdkProviderImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index e7eb2992..cf1638eb 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.susu.core.ui.extension.susuClickable -import com.susu.feature.loginsignup.KakaoLoginHelper +import com.susu.feature.loginsignup.social.KakaoLoginHelper import com.susu.feature.loginsignup.R @Composable diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt similarity index 98% rename from feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt rename to feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt index 48c35629..bc752a8c 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt @@ -1,4 +1,4 @@ -package com.susu.feature.loginsignup +package com.susu.feature.loginsignup.social import android.content.Context import com.kakao.sdk.common.model.AuthError diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt similarity index 96% rename from feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt rename to feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt index 8c682938..5ad33c4a 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/KakaoSdkProviderImpl.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt @@ -1,4 +1,4 @@ -package com.susu.feature.loginsignup +package com.susu.feature.loginsignup.social import com.kakao.sdk.auth.AuthApiClient import com.kakao.sdk.auth.TokenManagerProvider From 08dde4e48cb200eefd45b9e0309a67a994d3d4ab Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:43:42 +0900 Subject: [PATCH 35/60] =?UTF-8?q?chore:=20=EA=B8=B0=EC=A1=B4=20=EC=95=94?= =?UTF-8?q?=ED=98=B8=ED=99=94=20DataStore=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/data/extension/DataStoreExtension.kt | 35 ------------ .../com/susu/data/security/SecurityUtil.kt | 55 ------------------- 2 files changed, 90 deletions(-) delete mode 100644 data/src/main/java/com/susu/data/extension/DataStoreExtension.kt delete mode 100644 data/src/main/java/com/susu/data/security/SecurityUtil.kt diff --git a/data/src/main/java/com/susu/data/extension/DataStoreExtension.kt b/data/src/main/java/com/susu/data/extension/DataStoreExtension.kt deleted file mode 100644 index 8c31ac93..00000000 --- a/data/src/main/java/com/susu/data/extension/DataStoreExtension.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.susu.data.extension - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.MutablePreferences -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import com.susu.data.JsonSingleton -import com.susu.data.security.SecurityUtil -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -const val SEPARATOR = "|" - -suspend inline fun DataStore.secureEdit( - value: T, - crossinline editStore: (MutablePreferences, String) -> Unit, -) { - edit { - val encryptedValue = SecurityUtil.encrypt(Json.encodeToString(value)) - editStore.invoke(it, encryptedValue.joinToString(SEPARATOR)) - } -} - -inline fun Flow.secureMap( - crossinline fetchValue: (Preferences) -> String?, -): Flow { - return map { pref -> - fetchValue(pref)?.let { encryptedString -> - val decryptedValue = SecurityUtil.decrypt(encryptedString.split(SEPARATOR).map { it.toByte() }.toByteArray()) - JsonSingleton.instance.decodeFromString(decryptedValue) - } - } -} diff --git a/data/src/main/java/com/susu/data/security/SecurityUtil.kt b/data/src/main/java/com/susu/data/security/SecurityUtil.kt deleted file mode 100644 index 24c9a4da..00000000 --- a/data/src/main/java/com/susu/data/security/SecurityUtil.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.susu.data.security - -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties.BLOCK_MODE_GCM -import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE -import android.security.keystore.KeyProperties.KEY_ALGORITHM_AES -import android.security.keystore.KeyProperties.PURPOSE_DECRYPT -import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT -import java.security.Key -import java.security.KeyStore -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.spec.GCMParameterSpec - -object SecurityUtil { - - private const val KEYSTORE_NAME = "AndroidKeyStore" - private const val CIPHER_OPTION = "AES/GCM/NOPadding" - private const val SECRET_KEY = "SecretKey" - private const val CHARSET = "UTF-8" - private const val GCM_PARAM_LEN = 128 - - private val cipher = Cipher.getInstance(CIPHER_OPTION) - private val keyStore = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } - private val keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM_AES, KEYSTORE_NAME) - private val charset = charset(CHARSET) - - fun encrypt(data: String): ByteArray { - return cipher.apply { - init(Cipher.ENCRYPT_MODE, getSecretKey()) - }.doFinal(data.toByteArray(charset)) - } - - fun decrypt(encryptedData: ByteArray): String { - return cipher.apply { - init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(GCM_PARAM_LEN, cipher.iv)) - }.doFinal(encryptedData).toString(charset) - } - - private fun getSecretKey(): Key { - return if (keyStore.isKeyEntry(SECRET_KEY)) { - keyStore.getKey(SECRET_KEY, null) - } else { - keyGenerator.apply { - init( - KeyGenParameterSpec - .Builder(SECRET_KEY, PURPOSE_ENCRYPT or PURPOSE_DECRYPT) - .setBlockModes(BLOCK_MODE_GCM) - .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) - .build(), - ) - }.generateKey() - } - } -} From 076999acf78c9d443c604a5433bc62bc3f3e5b3d Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 18:50:02 +0900 Subject: [PATCH 36/60] =?UTF-8?q?chore:=20detekt=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/susu/data/network/TokenAuthenticator.kt | 2 +- .../main/java/com/susu/feature/loginsignup/di/KakaoModule.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt index 352ed7e1..baf6a0b8 100644 --- a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt +++ b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt @@ -12,7 +12,7 @@ import javax.inject.Inject class TokenAuthenticator @Inject constructor( private val tokenRepository: TokenRepository, - private val authService: AuthService + private val authService: AuthService, ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { // 1. refresh token으로 갱신 요청 diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt index 421ed760..70fdbdd6 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt @@ -15,7 +15,6 @@ abstract class KakaoModule { @Binds @Singleton abstract fun bindsKakaoLoginProvider( - kakaoLoginProviderImpl: KakaoSdkProviderImpl + kakaoLoginProviderImpl: KakaoSdkProviderImpl, ): KakaoSdkProvider } - From 98c82af0565373276babde5729a9bf8a3e1edd80 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 19:14:36 +0900 Subject: [PATCH 37/60] =?UTF-8?q?chore:=20gitignore=EC=97=90=20report,=20g?= =?UTF-8?q?oogle-service.json=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++++- app/.gitignore | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 91ba8e8b..ac08e187 100644 --- a/.gitignore +++ b/.gitignore @@ -153,4 +153,7 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar -# End of https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin \ No newline at end of file +# Compose-Report +/report + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin diff --git a/app/.gitignore b/app/.gitignore index e8866d24..f3bc2dbd 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,2 +1,3 @@ /build /src/main/res/values/app_key.xml +google-services.json From 3d4db6c34dd090154ccd6b32479d993e8d92d8d1 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 19:27:15 +0900 Subject: [PATCH 38/60] chore: ktlint trailing comma check --- data/src/main/java/com/susu/data/model/SnsProviders.kt | 2 +- .../main/java/com/susu/data/model/request/AccessTokenRequest.kt | 2 +- .../java/com/susu/data/model/request/RefreshTokenRequest.kt | 2 +- .../java/com/susu/data/model/response/ValidRegisterResponse.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/susu/data/model/SnsProviders.kt b/data/src/main/java/com/susu/data/model/SnsProviders.kt index b487b902..1072643f 100644 --- a/data/src/main/java/com/susu/data/model/SnsProviders.kt +++ b/data/src/main/java/com/susu/data/model/SnsProviders.kt @@ -1,5 +1,5 @@ package com.susu.data.model enum class SnsProviders(val path: String) { - Kakao("KAKAO") + Kakao("KAKAO"), } diff --git a/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt b/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt index 491f0381..43551d9e 100644 --- a/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt +++ b/data/src/main/java/com/susu/data/model/request/AccessTokenRequest.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class AccessTokenRequest( - val accessToken: String + val accessToken: String, ) diff --git a/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt b/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt index b176c2ce..c293de05 100644 --- a/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt +++ b/data/src/main/java/com/susu/data/model/request/RefreshTokenRequest.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class RefreshTokenRequest( - val refreshToken: String + val refreshToken: String, ) diff --git a/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt b/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt index 7f95d904..e5e6b3c1 100644 --- a/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt +++ b/data/src/main/java/com/susu/data/model/response/ValidRegisterResponse.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class ValidRegisterResponse( - val canRegister: Boolean + val canRegister: Boolean, ) From 66a84c99c254f274b86eefca07433811e884bdcd Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 26 Dec 2023 20:04:47 +0900 Subject: [PATCH 39/60] chore: ktlint Import order check --- .../com/susu/feature/loginsignup/login/LoginContract.kt | 2 +- .../com/susu/feature/loginsignup/login/LoginScreen.kt | 2 +- .../loginsignup/navigation/LoginSignupNavigation.kt | 2 +- .../com/susu/feature/loginsignup/signup/SignUpScreen.kt | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt index ad34134c..4a80759f 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -12,6 +12,6 @@ class LoginContract { data class LoginState( val isLoading: Boolean = false, - val showVote: Boolean = false + val showVote: Boolean = false, ) : UiState } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index cf1638eb..13f0e19c 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -21,8 +21,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.susu.core.ui.extension.susuClickable -import com.susu.feature.loginsignup.social.KakaoLoginHelper import com.susu.feature.loginsignup.R +import com.susu.feature.loginsignup.social.KakaoLoginHelper @Composable fun LoginScreen( diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt index f2139363..281be13b 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt @@ -5,9 +5,9 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.navigation -import com.susu.feature.loginsignup.test.TestScreen import com.susu.feature.loginsignup.login.LoginScreen import com.susu.feature.loginsignup.signup.SignUpScreen +import com.susu.feature.loginsignup.test.TestScreen @Suppress("unused") fun NavController.navigateLoginSignup(navOptions: NavOptions) { diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt index 71b3b892..6a5aebd6 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpScreen.kt @@ -36,9 +36,14 @@ fun SignUpScreen( } Column { - TextField(value = uiState.name, onValueChange = viewModel::updateName, label = { Text(text = "이름") }) TextField( - value = uiState.gender, onValueChange = viewModel::updateGender, + value = uiState.name, + onValueChange = viewModel::updateName, + label = { Text(text = "이름") }, + ) + TextField( + value = uiState.gender, + onValueChange = viewModel::updateGender, label = { Text(text = "성별 (M/F)") }, From 3a3a0141c92cfcb8e86bbe92213e1a330a976d4f Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 19:54:50 +0900 Subject: [PATCH 40/60] =?UTF-8?q?chore:=20local.properties=EB=A1=9C=20?= =?UTF-8?q?=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EC=95=B1=20=ED=82=A4=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.gitignore | 1 - app/build.gradle.kts | 7 +++++++ app/src/main/java/com/oksusu/susu/SUSUApplication.kt | 2 +- feature/loginsignup/.gitignore | 1 - feature/loginsignup/build.gradle.kts | 9 +++++++++ feature/loginsignup/src/main/AndroidManifest.xml | 2 +- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/.gitignore b/app/.gitignore index f3bc2dbd..a2def884 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,2 @@ /build -/src/main/res/values/app_key.xml google-services.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 343f5c91..e77b8fac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.susu.android.application) @@ -11,6 +13,7 @@ android { applicationId = "com.oksusu.susu" versionCode = 1 versionName = "1.0" + buildConfigField("String", "KAKAO_APP_KEY", getApiKey("KAKAO_APP_KEY")) } buildFeatures { @@ -29,3 +32,7 @@ dependencies { implementation(libs.timber) } + +fun getApiKey(propertyKey: String): String { + return gradleLocalProperties(rootDir).getProperty(propertyKey) +} diff --git a/app/src/main/java/com/oksusu/susu/SUSUApplication.kt b/app/src/main/java/com/oksusu/susu/SUSUApplication.kt index bc8ce439..8d39c0f1 100644 --- a/app/src/main/java/com/oksusu/susu/SUSUApplication.kt +++ b/app/src/main/java/com/oksusu/susu/SUSUApplication.kt @@ -14,6 +14,6 @@ class SUSUApplication : Application() { Timber.plant(CustomTimberTree()) } - KakaoSdk.init(this, getString(R.string.kakao_app_key)) + KakaoSdk.init(this, BuildConfig.KAKAO_APP_KEY) } } diff --git a/feature/loginsignup/.gitignore b/feature/loginsignup/.gitignore index e8866d24..796b96d1 100644 --- a/feature/loginsignup/.gitignore +++ b/feature/loginsignup/.gitignore @@ -1,2 +1 @@ /build -/src/main/res/values/app_key.xml diff --git a/feature/loginsignup/build.gradle.kts b/feature/loginsignup/build.gradle.kts index 0e8928cd..e779f278 100644 --- a/feature/loginsignup/build.gradle.kts +++ b/feature/loginsignup/build.gradle.kts @@ -1,3 +1,5 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.susu.android.feature.compose) @@ -5,8 +7,15 @@ plugins { android { namespace = "com.susu.feature.loginsignup" + defaultConfig { + manifestPlaceholders["KAKAO_APP_KEY"] = "kakao${getApiKey("KAKAO_APP_KEY")}" + } } dependencies { implementation(libs.kakao.sdk.user) } + +fun getApiKey(propertyKey: String): String { + return gradleLocalProperties(rootDir).getProperty(propertyKey) +} diff --git a/feature/loginsignup/src/main/AndroidManifest.xml b/feature/loginsignup/src/main/AndroidManifest.xml index ffdfe8f3..82ad0534 100644 --- a/feature/loginsignup/src/main/AndroidManifest.xml +++ b/feature/loginsignup/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ + android:scheme="${KAKAO_APP_KEY}" /> From 74f23ebaea4d056a246761b19a7a6b4caf8363d6 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 19:59:03 +0900 Subject: [PATCH 41/60] =?UTF-8?q?fix:=20merge=ED=95=98=EB=A9=B0=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20plugin=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e77b8fac..bb78b824 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,8 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { alias(libs.plugins.susu.android.application) alias(libs.plugins.susu.android.hilt) + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.crashlytics) } android { From 1dc78600182e6a78949b176b61745c22a83e10eb Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 21:43:32 +0900 Subject: [PATCH 42/60] =?UTF-8?q?feat:=20LoggingInterceptor=20level=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20di?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/NetworkModule.kt | 68 ++++++++----------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/data/src/main/java/com/susu/data/di/NetworkModule.kt b/data/src/main/java/com/susu/data/di/NetworkModule.kt index 1dbfd35c..36b55117 100644 --- a/data/src/main/java/com/susu/data/di/NetworkModule.kt +++ b/data/src/main/java/com/susu/data/di/NetworkModule.kt @@ -29,32 +29,34 @@ object NetworkModule { @Singleton @Provides - fun provideOkHttpClient( - tokenInterceptor: TokenInterceptor, - tokenAuthenticator: TokenAuthenticator, + fun provideLoggingInterceptor( json: Json, - ): OkHttpClient { - val loggingInterceptor = HttpLoggingInterceptor { message -> - when { - !message.isJsonObject() && !message.isJsonArray() -> - Timber.tag(RETROFIT_TAG).d("CONNECTION INFO -> $message") + ): HttpLoggingInterceptor = HttpLoggingInterceptor { message -> + when { + !message.isJsonObject() && !message.isJsonArray() -> + Timber.tag(RETROFIT_TAG).d("CONNECTION INFO -> $message") - else -> kotlin.runCatching { - json.encodeToString(Json.parseToJsonElement(message)) - }.onSuccess { - Timber.tag(RETROFIT_TAG).d(it) - }.onFailure { - Timber.tag(RETROFIT_TAG).d(message) - } + else -> kotlin.runCatching { + json.encodeToString(Json.parseToJsonElement(message)) + }.onSuccess { + Timber.tag(RETROFIT_TAG).d(it) + }.onFailure { + Timber.tag(RETROFIT_TAG).d(message) } } + }.apply { level = HttpLoggingInterceptor.Level.BODY } - return OkHttpClient.Builder() - .addInterceptor(tokenInterceptor) - .addInterceptor(loggingInterceptor) - .authenticator(tokenAuthenticator) - .build() - } + @Singleton + @Provides + fun provideOkHttpClient( + tokenInterceptor: TokenInterceptor, + tokenAuthenticator: TokenAuthenticator, + loggingInterceptor: HttpLoggingInterceptor, + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(tokenInterceptor) + .addInterceptor(loggingInterceptor) + .authenticator(tokenAuthenticator) + .build() @Singleton @Provides @@ -71,27 +73,11 @@ object NetworkModule { @Provides @AuthOkHttpClient fun provideAuthOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, json: Json, - ): OkHttpClient { - val loggingInterceptor = HttpLoggingInterceptor { message -> - when { - !message.isJsonObject() && !message.isJsonArray() -> - Timber.tag(RETROFIT_TAG).d("CONNECTION INFO -> $message") - - else -> kotlin.runCatching { - json.encodeToString(Json.parseToJsonElement(message)) - }.onSuccess { - Timber.tag(RETROFIT_TAG).d(it) - }.onFailure { - Timber.tag(RETROFIT_TAG).d(message) - } - } - } - - return OkHttpClient.Builder() - .addInterceptor(loggingInterceptor) - .build() - } + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() @Singleton @Provides From 84df3e7779929f9825914312dc59850126a8ecb4 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 21:54:48 +0900 Subject: [PATCH 43/60] =?UTF-8?q?refactor:=20kakao=20sdk=20=EC=8B=B1?= =?UTF-8?q?=EA=B8=80=ED=86=A4=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20di=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/susu/domain/util/KakaoSdkProvider.kt | 8 ---- .../feature/loginsignup/di/KakaoModule.kt | 20 -------- .../feature/loginsignup/login/LoginScreen.kt | 4 +- .../loginsignup/login/LoginViewModel.kt | 7 ++- .../loginsignup/signup/SignUpViewModel.kt | 5 +- .../loginsignup/social/KakaoLoginHelper.kt | 46 +++++++++++++++++-- .../social/KakaoSdkProviderImpl.kt | 43 ----------------- .../feature/loginsignup/test/TestViewModel.kt | 7 ++- feature/mypage/build.gradle.kts | 4 ++ 9 files changed, 57 insertions(+), 87 deletions(-) delete mode 100644 domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt diff --git a/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt b/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt deleted file mode 100644 index 54dd10fe..00000000 --- a/domain/src/main/java/com/susu/domain/util/KakaoSdkProvider.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.susu.domain.util - -interface KakaoSdkProvider { - fun hasKakaoLoginHistory(): Boolean - fun getAccessToken(callback: (String?) -> Unit) - fun logout(): Result - fun unlink(): Result -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt deleted file mode 100644 index 70fdbdd6..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/di/KakaoModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.susu.feature.loginsignup.di - -import com.susu.domain.util.KakaoSdkProvider -import com.susu.feature.loginsignup.social.KakaoSdkProviderImpl -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 KakaoModule { - - @Binds - @Singleton - abstract fun bindsKakaoLoginProvider( - kakaoLoginProviderImpl: KakaoSdkProviderImpl, - ): KakaoSdkProvider -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index 13f0e19c..5b28cd18 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -31,7 +31,6 @@ fun LoginScreen( navigateToSignUp: () -> Unit, ) { val context = LocalContext.current - val kakaoLoginHelper = KakaoLoginHelper(context) val uiState by viewModel.uiState.collectAsState() LaunchedEffect(key1 = viewModel.sideEffect) { @@ -62,7 +61,8 @@ fun LoginScreen( } else { KakaoLogin( onClick = { - kakaoLoginHelper.login( + KakaoLoginHelper.login( + context = context, onSuccess = { viewModel.login(it) }, onFailed = { viewModel.showToast(it) }, ) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index 185e79eb..d4d37c10 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository -import com.susu.domain.util.KakaoSdkProvider +import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -13,7 +13,6 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val kakaoSdkProvider: KakaoSdkProvider, private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, ) : BaseViewModel(LoginContract.LoginState()) { @@ -23,14 +22,14 @@ class LoginViewModel @Inject constructor( intent { copy(isLoading = true) } Timber.tag("AUTH").d("카카오 로그인 이력 확인") // 1. 카카오 토큰 존재 여부 - if (!kakaoSdkProvider.hasKakaoLoginHistory()) { + if (!KakaoLoginHelper.hasKakaoLoginHistory()) { // 1-1. 신규 유저 intent { copy(isLoading = false, showVote = true) } Timber.tag("AUTH").d("신규유져") } else { // 2. 카카오 access token 존재 시 로그인 시도 Timber.tag("AUTH").d("수수 로그인 시도") - kakaoSdkProvider.getAccessToken { + KakaoLoginHelper.getAccessToken { it?.let { accessToken -> viewModelScope.launch { authRepository.login(accessToken).onSuccess { token -> diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt index 05c4e2dd..4066a662 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt @@ -5,7 +5,7 @@ import com.susu.core.model.User import com.susu.core.ui.base.BaseViewModel import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository -import com.susu.domain.util.KakaoSdkProvider +import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -13,7 +13,6 @@ import javax.inject.Inject @HiltViewModel class SignUpViewModel @Inject constructor( - private val kakaoSdkProvider: KakaoSdkProvider, private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, ) : BaseViewModel(SignUpContract.SignUpState()) { @@ -31,7 +30,7 @@ class SignUpViewModel @Inject constructor( } fun signUp() { - kakaoSdkProvider.getAccessToken { + KakaoLoginHelper.getAccessToken { viewModelScope.launch { if (it != null) { authRepository.signUp( diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt index bc752a8c..a541e466 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt @@ -1,17 +1,18 @@ package com.susu.feature.loginsignup.social import android.content.Context +import com.kakao.sdk.auth.AuthApiClient +import com.kakao.sdk.auth.TokenManagerProvider import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient import timber.log.Timber -class KakaoLoginHelper( - private val context: Context, -) { +internal object KakaoLoginHelper { fun login( + context: Context, onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -25,20 +26,24 @@ class KakaoLoginHelper( loginWithKakaoAccount( onSuccess = onSuccess, onFailed = onFailed, + context = context ) } }, + context = context, ) } else { // 카카오톡 미설치 시 카카오계정 로그인 시도 Timber.tag("AUTH").d("카카오톡 미설치") loginWithKakaoAccount( onSuccess = onSuccess, onFailed = onFailed, + context = context, ) } } private fun loginWithKakaoTalk( + context: Context, onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -53,6 +58,7 @@ class KakaoLoginHelper( } private fun loginWithKakaoAccount( + context: Context, onSuccess: (String) -> Unit, onFailed: (Throwable?) -> Unit, ) { @@ -64,4 +70,38 @@ class KakaoLoginHelper( } } } + + fun hasKakaoLoginHistory(): Boolean { + return AuthApiClient.instance.hasToken() + } + + fun getAccessToken( + callback: (String?) -> Unit, + ) { + UserApiClient.instance.accessTokenInfo { _, error -> + if (error != null) { + callback(null) + } else { + // 토큰 유효성 체크 성공(필요 시 토큰 갱신됨) + callback(TokenManagerProvider.instance.manager.getToken()?.accessToken) + } + } + } + + // 기능 테스트를 위함. + fun logout() = runCatching { + UserApiClient.instance.logout { error -> + if (error != null) { + throw error + } + } + } + + fun unlink() = runCatching { + UserApiClient.instance.unlink { error -> + if (error != null) { + throw error + } + } + } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt deleted file mode 100644 index 5ad33c4a..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoSdkProviderImpl.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.susu.feature.loginsignup.social - -import com.kakao.sdk.auth.AuthApiClient -import com.kakao.sdk.auth.TokenManagerProvider -import com.kakao.sdk.user.UserApiClient -import com.susu.domain.util.KakaoSdkProvider -import javax.inject.Inject - -class KakaoSdkProviderImpl @Inject constructor() : KakaoSdkProvider { - - override fun hasKakaoLoginHistory(): Boolean { - return AuthApiClient.instance.hasToken() - } - - override fun getAccessToken( - callback: (String?) -> Unit, - ) { - UserApiClient.instance.accessTokenInfo { _, error -> - if (error != null) { - callback(null) - } else { - // 토큰 유효성 체크 성공(필요 시 토큰 갱신됨) - callback(TokenManagerProvider.instance.manager.getToken()?.accessToken) - } - } - } - - override fun logout() = runCatching { - UserApiClient.instance.logout { error -> - if (error != null) { - throw error - } - } - } - - override fun unlink() = runCatching { - UserApiClient.instance.unlink { error -> - if (error != null) { - throw error - } - } - } -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt index ffaa83f0..27286b78 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel import com.susu.domain.repository.AuthRepository import com.susu.domain.repository.TokenRepository -import com.susu.domain.util.KakaoSdkProvider +import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -15,19 +15,18 @@ import javax.inject.Inject class TestViewModel @Inject constructor( private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, - private val kakaoSdkProvider: KakaoSdkProvider, ) : BaseViewModel(TestContract.TestState) { fun logout() { viewModelScope.launch { authRepository.logout() - kakaoSdkProvider.logout() + KakaoLoginHelper.logout() tokenRepository.deleteTokens() } postSideEffect(TestContract.TestEffect.NavigateToLogin) } fun withdraw() { - kakaoSdkProvider.unlink().onSuccess { + KakaoLoginHelper.unlink().onSuccess { viewModelScope.launch { runBlocking { authRepository.withdraw() } tokenRepository.deleteTokens() diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index be3c156f..6bfd827c 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -6,3 +6,7 @@ plugins { android { namespace = "com.susu.feature.mypage" } + +dependencies { + implementation(libs.kakao.sdk.user) +} From 2e18684bea6c61dca6bcc913c2f5f1d66468c125 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 22:01:35 +0900 Subject: [PATCH 44/60] =?UTF-8?q?refactor:=20NetworkModule=EC=97=90?= =?UTF-8?q?=EC=84=9C=20service=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/NetworkModule.kt | 14 ---------- .../java/com/susu/data/di/ServiceModule.kt | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 data/src/main/java/com/susu/data/di/ServiceModule.kt diff --git a/data/src/main/java/com/susu/data/di/NetworkModule.kt b/data/src/main/java/com/susu/data/di/NetworkModule.kt index 36b55117..3541ac94 100644 --- a/data/src/main/java/com/susu/data/di/NetworkModule.kt +++ b/data/src/main/java/com/susu/data/di/NetworkModule.kt @@ -4,8 +4,6 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.susu.data.Constants.RETROFIT_TAG import com.susu.data.extension.isJsonArray import com.susu.data.extension.isJsonObject -import com.susu.data.network.ApiService -import com.susu.data.network.AuthService import com.susu.data.network.TokenAuthenticator import com.susu.data.network.TokenInterceptor import dagger.Module @@ -100,16 +98,4 @@ object NetworkModule { ignoreUnknownKeys = true } } - - @Singleton - @Provides - fun provideApiService(retrofit: Retrofit): ApiService { - return retrofit.create(ApiService::class.java) - } - - @Singleton - @Provides - fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthService { - return retrofit.create(AuthService::class.java) - } } diff --git a/data/src/main/java/com/susu/data/di/ServiceModule.kt b/data/src/main/java/com/susu/data/di/ServiceModule.kt new file mode 100644 index 00000000..7d6e5d0a --- /dev/null +++ b/data/src/main/java/com/susu/data/di/ServiceModule.kt @@ -0,0 +1,27 @@ +package com.susu.data.di + +import com.susu.data.network.ApiService +import com.susu.data.network.AuthService +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 ServiceModule { + + @Singleton + @Provides + fun provideApiService(retrofit: Retrofit): ApiService { + return retrofit.create(ApiService::class.java) + } + + @Singleton + @Provides + fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthService { + return retrofit.create(AuthService::class.java) + } +} From af6d54f69c0c92d4f97f76747513c173c42506f4 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 22:09:47 +0900 Subject: [PATCH 45/60] =?UTF-8?q?refactor:=20Service=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/ServiceModule.kt | 19 +++++++++++----- .../susu/data/network/TokenAuthenticator.kt | 5 +++-- .../SignUpService.kt} | 18 ++------------- .../TokenService.kt} | 4 ++-- .../susu/data/network/service/UserService.kt | 22 +++++++++++++++++++ .../data/repository/AuthRepositoryImpl.kt | 16 ++++++++------ 6 files changed, 51 insertions(+), 33 deletions(-) rename data/src/main/java/com/susu/data/network/{ApiService.kt => service/SignUpService.kt} (60%) rename data/src/main/java/com/susu/data/network/{AuthService.kt => service/TokenService.kt} (84%) create mode 100644 data/src/main/java/com/susu/data/network/service/UserService.kt diff --git a/data/src/main/java/com/susu/data/di/ServiceModule.kt b/data/src/main/java/com/susu/data/di/ServiceModule.kt index 7d6e5d0a..eb343916 100644 --- a/data/src/main/java/com/susu/data/di/ServiceModule.kt +++ b/data/src/main/java/com/susu/data/di/ServiceModule.kt @@ -1,7 +1,8 @@ package com.susu.data.di -import com.susu.data.network.ApiService -import com.susu.data.network.AuthService +import com.susu.data.network.service.UserService +import com.susu.data.network.service.TokenService +import com.susu.data.network.service.SignUpService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -15,13 +16,19 @@ object ServiceModule { @Singleton @Provides - fun provideApiService(retrofit: Retrofit): ApiService { - return retrofit.create(ApiService::class.java) + fun provideUserService(retrofit: Retrofit): UserService { + return retrofit.create(UserService::class.java) } @Singleton @Provides - fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthService { - return retrofit.create(AuthService::class.java) + fun provideSignUpService(retrofit: Retrofit): SignUpService { + return retrofit.create(SignUpService::class.java) + } + + @Singleton + @Provides + fun provideTokenService(@AuthRetrofit retrofit: Retrofit): TokenService { + return retrofit.create(TokenService::class.java) } } diff --git a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt index baf6a0b8..f9a58a0e 100644 --- a/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt +++ b/data/src/main/java/com/susu/data/network/TokenAuthenticator.kt @@ -1,6 +1,7 @@ package com.susu.data.network import com.susu.data.model.request.RefreshTokenRequest +import com.susu.data.network.service.TokenService import com.susu.domain.repository.TokenRepository import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking @@ -12,7 +13,7 @@ import javax.inject.Inject class TokenAuthenticator @Inject constructor( private val tokenRepository: TokenRepository, - private val authService: AuthService, + private val tokenService: TokenService, ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { // 1. refresh token으로 갱신 요청 @@ -25,7 +26,7 @@ class TokenAuthenticator @Inject constructor( return runBlocking { // 2. access token 갱신 - val tokenResponse = authService.refreshAccessToken(RefreshTokenRequest(refreshToken)) + val tokenResponse = tokenService.refreshAccessToken(RefreshTokenRequest(refreshToken)) // 2-1. 정상적으로 받지 못하면 request token 까지 만료된 것. if (tokenResponse.isSuccessful.not() || tokenResponse.body() == null) { diff --git a/data/src/main/java/com/susu/data/network/ApiService.kt b/data/src/main/java/com/susu/data/network/service/SignUpService.kt similarity index 60% rename from data/src/main/java/com/susu/data/network/ApiService.kt rename to data/src/main/java/com/susu/data/network/service/SignUpService.kt index 8d6fdc5e..c6aa09f5 100644 --- a/data/src/main/java/com/susu/data/network/ApiService.kt +++ b/data/src/main/java/com/susu/data/network/service/SignUpService.kt @@ -1,23 +1,15 @@ -package com.susu.data.network +package com.susu.data.network.service import com.susu.data.model.TokenEntity import com.susu.data.model.UserEntity -import com.susu.data.model.request.AccessTokenRequest import com.susu.data.model.response.ValidRegisterResponse -import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query -interface ApiService { - @POST("oauth/{provider}/login") - suspend fun login( - @Path("provider") provider: String, - @Body accessTokenRequest: AccessTokenRequest, - ): TokenEntity - +interface SignUpService { @POST("oauth/{provider}/sign-up") suspend fun signUp( @Path("provider") provider: String, @@ -30,10 +22,4 @@ interface ApiService { @Path("provider") provider: String, @Query("accessToken") accessToken: String, ): ValidRegisterResponse - - @POST("auth/logout") - suspend fun logout(): Response - - @POST("auth/withdraw") - suspend fun withdraw(): Response } diff --git a/data/src/main/java/com/susu/data/network/AuthService.kt b/data/src/main/java/com/susu/data/network/service/TokenService.kt similarity index 84% rename from data/src/main/java/com/susu/data/network/AuthService.kt rename to data/src/main/java/com/susu/data/network/service/TokenService.kt index 0c54f2e9..6ae7ec86 100644 --- a/data/src/main/java/com/susu/data/network/AuthService.kt +++ b/data/src/main/java/com/susu/data/network/service/TokenService.kt @@ -1,4 +1,4 @@ -package com.susu.data.network +package com.susu.data.network.service import com.susu.data.model.TokenEntity import com.susu.data.model.request.RefreshTokenRequest @@ -6,7 +6,7 @@ import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST -interface AuthService { +interface TokenService { @POST("auth/token/refresh") suspend fun refreshAccessToken( @Body refreshTokenRequest: RefreshTokenRequest, diff --git a/data/src/main/java/com/susu/data/network/service/UserService.kt b/data/src/main/java/com/susu/data/network/service/UserService.kt new file mode 100644 index 00000000..7d1965d5 --- /dev/null +++ b/data/src/main/java/com/susu/data/network/service/UserService.kt @@ -0,0 +1,22 @@ +package com.susu.data.network.service + +import com.susu.data.model.TokenEntity +import com.susu.data.model.request.AccessTokenRequest +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +interface UserService { + @POST("oauth/{provider}/login") + suspend fun login( + @Path("provider") provider: String, + @Body accessTokenRequest: AccessTokenRequest, + ): TokenEntity + + @POST("auth/logout") + suspend fun logout(): Response + + @POST("auth/withdraw") + suspend fun withdraw(): Response +} diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 2bc307e5..5ba40833 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -5,17 +5,19 @@ import com.susu.data.model.SnsProviders import com.susu.data.model.request.AccessTokenRequest import com.susu.data.model.toData import com.susu.data.model.toDomain -import com.susu.data.network.ApiService +import com.susu.data.network.service.SignUpService +import com.susu.data.network.service.UserService import com.susu.domain.repository.AuthRepository import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( - private val apiService: ApiService, + private val userService: UserService, + private val signUpService: SignUpService, ) : AuthRepository { override suspend fun login( accessToken: String, ) = kotlin.runCatching { - apiService.login( + userService.login( provider = SnsProviders.Kakao.path, accessTokenRequest = AccessTokenRequest(accessToken), ).toDomain() @@ -25,7 +27,7 @@ class AuthRepositoryImpl @Inject constructor( snsAccessToken: String, user: User, ) = runCatching { - apiService.signUp( + signUpService.signUp( provider = SnsProviders.Kakao.path, accessToken = snsAccessToken, user = user.toData(), @@ -35,17 +37,17 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun canRegister( accessToken: String, ): Boolean { - return apiService.checkValidRegister( + return signUpService.checkValidRegister( provider = SnsProviders.Kakao.path, accessToken = accessToken, ).canRegister } override suspend fun logout() { - apiService.logout() + userService.logout() } override suspend fun withdraw() { - apiService.withdraw() + userService.withdraw() } } From 5d585ab24f0123056b1276d6d545ce75b3a0ab7e Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 22:12:46 +0900 Subject: [PATCH 46/60] =?UTF-8?q?chore:=20Contract=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?class=20->=20sealed=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/feature/loginsignup/login/LoginContract.kt | 2 +- .../java/com/susu/feature/loginsignup/signup/SignUpContract.kt | 2 +- .../main/java/com/susu/feature/loginsignup/test/TestContract.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt index 4a80759f..0b4748bd 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -3,7 +3,7 @@ package com.susu.feature.loginsignup.login import com.susu.core.ui.base.SideEffect import com.susu.core.ui.base.UiState -class LoginContract { +sealed interface LoginContract { sealed class LoginEffect : SideEffect { data object NavigateToReceived : LoginEffect() data object NavigateToSignUp : LoginEffect() diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt index 964aeb1c..d0bc2ff9 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpContract.kt @@ -3,7 +3,7 @@ package com.susu.feature.loginsignup.signup import com.susu.core.ui.base.SideEffect import com.susu.core.ui.base.UiState -class SignUpContract { +sealed interface SignUpContract { sealed class SignUpEffect : SideEffect { data object NavigateToReceived : SignUpEffect() data class ShowToast(val msg: String) : SignUpEffect() diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt index 80cc5677..2136d5e7 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt @@ -3,7 +3,7 @@ package com.susu.feature.loginsignup.test import com.susu.core.ui.base.SideEffect import com.susu.core.ui.base.UiState -class TestContract { +sealed interface TestContract { sealed class TestEffect : SideEffect { data object NavigateToLogin : TestEffect() } From 6128d6743f5d4cd44b7b13564f4637b61adb34a0 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 22:14:21 +0900 Subject: [PATCH 47/60] =?UTF-8?q?chore:=20sns=20->=20oauth=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/data/repository/AuthRepositoryImpl.kt | 4 ++-- .../main/java/com/susu/domain/repository/AuthRepository.kt | 2 +- .../com/susu/feature/loginsignup/signup/SignUpViewModel.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 5ba40833..5c81a544 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -24,12 +24,12 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun signUp( - snsAccessToken: String, + oauthAccessToken: String, user: User, ) = runCatching { signUpService.signUp( provider = SnsProviders.Kakao.path, - accessToken = snsAccessToken, + accessToken = oauthAccessToken, user = user.toData(), ).toDomain() } diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt index d7ec259a..574b8b27 100644 --- a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt @@ -5,7 +5,7 @@ import com.susu.core.model.User interface AuthRepository { suspend fun login(accessToken: String): Result - suspend fun signUp(snsAccessToken: String, user: User): Result + suspend fun signUp(oauthAccessToken: String, user: User): Result suspend fun canRegister(accessToken: String): Boolean suspend fun logout() suspend fun withdraw() diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt index 4066a662..ed9e1c24 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt @@ -34,7 +34,7 @@ class SignUpViewModel @Inject constructor( viewModelScope.launch { if (it != null) { authRepository.signUp( - snsAccessToken = it, + oauthAccessToken = it, user = User( name = uiState.value.name, gender = uiState.value.gender, From 9f16e9754efe273c7d5eed0468acf0971adf02f5 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Sun, 31 Dec 2023 22:16:26 +0900 Subject: [PATCH 48/60] =?UTF-8?q?chore:=20gitignore=EC=97=90=20idea=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용하는 플러그인들에서 자꾸 파일이 생성되어서 추가합니다. 이전 프로젝트들에서는 항상 idea 폴더를 통으로 뺐어요! --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac08e187..506ff3d3 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ obj/ /out/ # User-specific configurations +/.idea .idea/caches/ .idea/libraries/ .idea/shelf/ From 67a4348db16e1ab86cf6a3fdec7330b921ecc10a Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 12:18:26 +0900 Subject: [PATCH 49/60] =?UTF-8?q?refactor:=20TokenEntity=20->=20TokenRespo?= =?UTF-8?q?nse=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=EA=B3=BC?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EA=B8=B0=EA=B0=84=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8D=BC=ED=8B=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/core/model/Token.kt | 2 -- .../java/com/susu/data/model/TokenEntity.kt | 26 ------------------- .../java/com/susu/data/model/TokenResponse.kt | 15 +++++++++++ .../data/network/service/SignUpService.kt | 4 +-- .../susu/data/network/service/TokenService.kt | 4 +-- .../susu/data/network/service/UserService.kt | 4 +-- 6 files changed, 21 insertions(+), 34 deletions(-) delete mode 100644 data/src/main/java/com/susu/data/model/TokenEntity.kt create mode 100644 data/src/main/java/com/susu/data/model/TokenResponse.kt diff --git a/core/model/src/main/java/com/susu/core/model/Token.kt b/core/model/src/main/java/com/susu/core/model/Token.kt index 59b90874..5c20da3a 100644 --- a/core/model/src/main/java/com/susu/core/model/Token.kt +++ b/core/model/src/main/java/com/susu/core/model/Token.kt @@ -2,7 +2,5 @@ package com.susu.core.model data class Token( val accessToken: String, - val accessTokenExp: String, val refreshToken: String, - val refreshTokenExp: String, ) diff --git a/data/src/main/java/com/susu/data/model/TokenEntity.kt b/data/src/main/java/com/susu/data/model/TokenEntity.kt deleted file mode 100644 index 3e8379ef..00000000 --- a/data/src/main/java/com/susu/data/model/TokenEntity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.susu.data.model - -import com.susu.core.model.Token -import kotlinx.serialization.Serializable - -@Serializable -data class TokenEntity( - val accessToken: String, - val accessTokenExp: String, - val refreshToken: String, - val refreshTokenExp: String, -) - -fun TokenEntity.toDomain() = Token( - accessToken = accessToken, - accessTokenExp = accessTokenExp, - refreshToken = refreshToken, - refreshTokenExp = refreshTokenExp, -) - -fun Token.toData() = TokenEntity( - accessToken = accessToken, - accessTokenExp = accessTokenExp, - refreshToken = refreshToken, - refreshTokenExp = refreshTokenExp, -) diff --git a/data/src/main/java/com/susu/data/model/TokenResponse.kt b/data/src/main/java/com/susu/data/model/TokenResponse.kt new file mode 100644 index 00000000..476cc973 --- /dev/null +++ b/data/src/main/java/com/susu/data/model/TokenResponse.kt @@ -0,0 +1,15 @@ +package com.susu.data.model + +import com.susu.core.model.Token +import kotlinx.serialization.Serializable + +@Serializable +data class TokenResponse( + val accessToken: String, + val refreshToken: String, +) + +fun TokenResponse.toDomain() = Token( + accessToken = accessToken, + refreshToken = refreshToken, +) diff --git a/data/src/main/java/com/susu/data/network/service/SignUpService.kt b/data/src/main/java/com/susu/data/network/service/SignUpService.kt index c6aa09f5..86d70e08 100644 --- a/data/src/main/java/com/susu/data/network/service/SignUpService.kt +++ b/data/src/main/java/com/susu/data/network/service/SignUpService.kt @@ -1,6 +1,6 @@ package com.susu.data.network.service -import com.susu.data.model.TokenEntity +import com.susu.data.model.TokenResponse import com.susu.data.model.UserEntity import com.susu.data.model.response.ValidRegisterResponse import retrofit2.http.Body @@ -15,7 +15,7 @@ interface SignUpService { @Path("provider") provider: String, @Query("accessToken") accessToken: String, @Body user: UserEntity, - ): TokenEntity + ): TokenResponse @GET("oauth/{provider}/sign-up/valid") suspend fun checkValidRegister( diff --git a/data/src/main/java/com/susu/data/network/service/TokenService.kt b/data/src/main/java/com/susu/data/network/service/TokenService.kt index 6ae7ec86..d7fb2156 100644 --- a/data/src/main/java/com/susu/data/network/service/TokenService.kt +++ b/data/src/main/java/com/susu/data/network/service/TokenService.kt @@ -1,6 +1,6 @@ package com.susu.data.network.service -import com.susu.data.model.TokenEntity +import com.susu.data.model.TokenResponse import com.susu.data.model.request.RefreshTokenRequest import retrofit2.Response import retrofit2.http.Body @@ -10,5 +10,5 @@ interface TokenService { @POST("auth/token/refresh") suspend fun refreshAccessToken( @Body refreshTokenRequest: RefreshTokenRequest, - ): Response + ): Response } diff --git a/data/src/main/java/com/susu/data/network/service/UserService.kt b/data/src/main/java/com/susu/data/network/service/UserService.kt index 7d1965d5..73a4059f 100644 --- a/data/src/main/java/com/susu/data/network/service/UserService.kt +++ b/data/src/main/java/com/susu/data/network/service/UserService.kt @@ -1,6 +1,6 @@ package com.susu.data.network.service -import com.susu.data.model.TokenEntity +import com.susu.data.model.TokenResponse import com.susu.data.model.request.AccessTokenRequest import retrofit2.Response import retrofit2.http.Body @@ -12,7 +12,7 @@ interface UserService { suspend fun login( @Path("provider") provider: String, @Body accessTokenRequest: AccessTokenRequest, - ): TokenEntity + ): TokenResponse @POST("auth/logout") suspend fun logout(): Response From 449c04c27cfae395a4fbcbbb9d1261c74ae30774 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 12:20:38 +0900 Subject: [PATCH 50/60] refactor: UserEntity -> UserRequest --- .../model/{UserEntity.kt => request/UserRequest.kt} | 12 +++--------- .../susu/data/model/{ => response}/TokenResponse.kt | 2 +- .../com/susu/data/network/service/SignUpService.kt | 6 +++--- .../com/susu/data/network/service/TokenService.kt | 2 +- .../com/susu/data/network/service/UserService.kt | 2 +- .../com/susu/data/repository/AuthRepositoryImpl.kt | 4 ++-- 6 files changed, 11 insertions(+), 17 deletions(-) rename data/src/main/java/com/susu/data/model/{UserEntity.kt => request/UserRequest.kt} (55%) rename data/src/main/java/com/susu/data/model/{ => response}/TokenResponse.kt (88%) diff --git a/data/src/main/java/com/susu/data/model/UserEntity.kt b/data/src/main/java/com/susu/data/model/request/UserRequest.kt similarity index 55% rename from data/src/main/java/com/susu/data/model/UserEntity.kt rename to data/src/main/java/com/susu/data/model/request/UserRequest.kt index 038cb9e1..d84198f4 100644 --- a/data/src/main/java/com/susu/data/model/UserEntity.kt +++ b/data/src/main/java/com/susu/data/model/request/UserRequest.kt @@ -1,22 +1,16 @@ -package com.susu.data.model +package com.susu.data.model.request import com.susu.core.model.User import kotlinx.serialization.Serializable @Serializable -data class UserEntity( +data class UserRequest( val name: String, val gender: String, val birth: Int, ) -fun UserEntity.toDomain() = User( - name = name, - gender = gender, - birth = birth, -) - -fun User.toData() = UserEntity( +fun User.toData() = UserRequest( name = name, gender = gender, birth = birth, diff --git a/data/src/main/java/com/susu/data/model/TokenResponse.kt b/data/src/main/java/com/susu/data/model/response/TokenResponse.kt similarity index 88% rename from data/src/main/java/com/susu/data/model/TokenResponse.kt rename to data/src/main/java/com/susu/data/model/response/TokenResponse.kt index 476cc973..7b17834a 100644 --- a/data/src/main/java/com/susu/data/model/TokenResponse.kt +++ b/data/src/main/java/com/susu/data/model/response/TokenResponse.kt @@ -1,4 +1,4 @@ -package com.susu.data.model +package com.susu.data.model.response import com.susu.core.model.Token import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/susu/data/network/service/SignUpService.kt b/data/src/main/java/com/susu/data/network/service/SignUpService.kt index 86d70e08..44c717fc 100644 --- a/data/src/main/java/com/susu/data/network/service/SignUpService.kt +++ b/data/src/main/java/com/susu/data/network/service/SignUpService.kt @@ -1,7 +1,7 @@ package com.susu.data.network.service -import com.susu.data.model.TokenResponse -import com.susu.data.model.UserEntity +import com.susu.data.model.response.TokenResponse +import com.susu.data.model.request.UserRequest import com.susu.data.model.response.ValidRegisterResponse import retrofit2.http.Body import retrofit2.http.GET @@ -14,7 +14,7 @@ interface SignUpService { suspend fun signUp( @Path("provider") provider: String, @Query("accessToken") accessToken: String, - @Body user: UserEntity, + @Body user: UserRequest, ): TokenResponse @GET("oauth/{provider}/sign-up/valid") diff --git a/data/src/main/java/com/susu/data/network/service/TokenService.kt b/data/src/main/java/com/susu/data/network/service/TokenService.kt index d7fb2156..b3ba04a8 100644 --- a/data/src/main/java/com/susu/data/network/service/TokenService.kt +++ b/data/src/main/java/com/susu/data/network/service/TokenService.kt @@ -1,6 +1,6 @@ package com.susu.data.network.service -import com.susu.data.model.TokenResponse +import com.susu.data.model.response.TokenResponse import com.susu.data.model.request.RefreshTokenRequest import retrofit2.Response import retrofit2.http.Body diff --git a/data/src/main/java/com/susu/data/network/service/UserService.kt b/data/src/main/java/com/susu/data/network/service/UserService.kt index 73a4059f..7b147de2 100644 --- a/data/src/main/java/com/susu/data/network/service/UserService.kt +++ b/data/src/main/java/com/susu/data/network/service/UserService.kt @@ -1,6 +1,6 @@ package com.susu.data.network.service -import com.susu.data.model.TokenResponse +import com.susu.data.model.response.TokenResponse import com.susu.data.model.request.AccessTokenRequest import retrofit2.Response import retrofit2.http.Body diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt index 5c81a544..21a5ef90 100644 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt @@ -3,8 +3,8 @@ package com.susu.data.repository import com.susu.core.model.User import com.susu.data.model.SnsProviders import com.susu.data.model.request.AccessTokenRequest -import com.susu.data.model.toData -import com.susu.data.model.toDomain +import com.susu.data.model.request.toData +import com.susu.data.model.response.toDomain import com.susu.data.network.service.SignUpService import com.susu.data.network.service.UserService import com.susu.domain.repository.AuthRepository From 4ad776bc2348faf9933483437940d411979ef32e Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 13:09:58 +0900 Subject: [PATCH 51/60] =?UTF-8?q?refactor:=20AuthRepository=EB=A5=BC=20Log?= =?UTF-8?q?in=EA=B3=BC=20SignUp=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/AuthRepositoryImpl.kt | 53 ------------------- .../data/repository/LoginRepositoryImpl.kt | 27 ++++++++++ .../data/repository/SignUpRepositoryImpl.kt | 32 +++++++++++ .../susu/domain/repository/AuthRepository.kt | 12 ----- .../susu/domain/repository/LoginRepository.kt | 9 ++++ .../domain/repository/SignUpRepository.kt | 9 ++++ 6 files changed, 77 insertions(+), 65 deletions(-) delete mode 100644 data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt create mode 100644 data/src/main/java/com/susu/data/repository/LoginRepositoryImpl.kt create mode 100644 data/src/main/java/com/susu/data/repository/SignUpRepositoryImpl.kt delete mode 100644 domain/src/main/java/com/susu/domain/repository/AuthRepository.kt create mode 100644 domain/src/main/java/com/susu/domain/repository/LoginRepository.kt create mode 100644 domain/src/main/java/com/susu/domain/repository/SignUpRepository.kt diff --git a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt deleted file mode 100644 index 21a5ef90..00000000 --- a/data/src/main/java/com/susu/data/repository/AuthRepositoryImpl.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.susu.data.repository - -import com.susu.core.model.User -import com.susu.data.model.SnsProviders -import com.susu.data.model.request.AccessTokenRequest -import com.susu.data.model.request.toData -import com.susu.data.model.response.toDomain -import com.susu.data.network.service.SignUpService -import com.susu.data.network.service.UserService -import com.susu.domain.repository.AuthRepository -import javax.inject.Inject - -class AuthRepositoryImpl @Inject constructor( - private val userService: UserService, - private val signUpService: SignUpService, -) : AuthRepository { - override suspend fun login( - accessToken: String, - ) = kotlin.runCatching { - userService.login( - provider = SnsProviders.Kakao.path, - accessTokenRequest = AccessTokenRequest(accessToken), - ).toDomain() - } - - override suspend fun signUp( - oauthAccessToken: String, - user: User, - ) = runCatching { - signUpService.signUp( - provider = SnsProviders.Kakao.path, - accessToken = oauthAccessToken, - user = user.toData(), - ).toDomain() - } - - override suspend fun canRegister( - accessToken: String, - ): Boolean { - return signUpService.checkValidRegister( - provider = SnsProviders.Kakao.path, - accessToken = accessToken, - ).canRegister - } - - override suspend fun logout() { - userService.logout() - } - - override suspend fun withdraw() { - userService.withdraw() - } -} diff --git a/data/src/main/java/com/susu/data/repository/LoginRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/LoginRepositoryImpl.kt new file mode 100644 index 00000000..ccb6b4ad --- /dev/null +++ b/data/src/main/java/com/susu/data/repository/LoginRepositoryImpl.kt @@ -0,0 +1,27 @@ +package com.susu.data.repository + +import com.susu.data.model.SnsProviders +import com.susu.data.model.request.AccessTokenRequest +import com.susu.data.model.response.toDomain +import com.susu.data.network.service.UserService +import com.susu.domain.repository.LoginRepository +import javax.inject.Inject + +class LoginRepositoryImpl @Inject constructor( + private val userService: UserService, +) : LoginRepository { + override suspend fun login( + oauthAccessToken: String, + ) = userService.login( + provider = SnsProviders.Kakao.path, + accessTokenRequest = AccessTokenRequest(oauthAccessToken), + ).toDomain() + + override suspend fun logout() { + userService.logout() + } + + override suspend fun withdraw() { + userService.withdraw() + } +} diff --git a/data/src/main/java/com/susu/data/repository/SignUpRepositoryImpl.kt b/data/src/main/java/com/susu/data/repository/SignUpRepositoryImpl.kt new file mode 100644 index 00000000..df8541c6 --- /dev/null +++ b/data/src/main/java/com/susu/data/repository/SignUpRepositoryImpl.kt @@ -0,0 +1,32 @@ +package com.susu.data.repository + +import com.susu.core.model.User +import com.susu.data.model.SnsProviders +import com.susu.data.model.request.toData +import com.susu.data.model.response.toDomain +import com.susu.data.network.service.SignUpService +import com.susu.domain.repository.SignUpRepository +import javax.inject.Inject + +class SignUpRepositoryImpl @Inject constructor( + private val signUpService: SignUpService, +) : SignUpRepository { + + override suspend fun signUp( + oauthAccessToken: String, + user: User, + ) = signUpService.signUp( + provider = SnsProviders.Kakao.path, + accessToken = oauthAccessToken, + user = user.toData(), + ).toDomain() + + override suspend fun canRegister( + oauthAccessToken: String, + ): Boolean { + return signUpService.checkValidRegister( + provider = SnsProviders.Kakao.path, + accessToken = oauthAccessToken, + ).canRegister + } +} diff --git a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt b/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt deleted file mode 100644 index 574b8b27..00000000 --- a/domain/src/main/java/com/susu/domain/repository/AuthRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.susu.domain.repository - -import com.susu.core.model.Token -import com.susu.core.model.User - -interface AuthRepository { - suspend fun login(accessToken: String): Result - suspend fun signUp(oauthAccessToken: String, user: User): Result - suspend fun canRegister(accessToken: String): Boolean - suspend fun logout() - suspend fun withdraw() -} diff --git a/domain/src/main/java/com/susu/domain/repository/LoginRepository.kt b/domain/src/main/java/com/susu/domain/repository/LoginRepository.kt new file mode 100644 index 00000000..b1d82189 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/repository/LoginRepository.kt @@ -0,0 +1,9 @@ +package com.susu.domain.repository + +import com.susu.core.model.Token + +interface LoginRepository { + suspend fun login(oauthAccessToken: String): Token + suspend fun logout() + suspend fun withdraw() +} diff --git a/domain/src/main/java/com/susu/domain/repository/SignUpRepository.kt b/domain/src/main/java/com/susu/domain/repository/SignUpRepository.kt new file mode 100644 index 00000000..8bfef54b --- /dev/null +++ b/domain/src/main/java/com/susu/domain/repository/SignUpRepository.kt @@ -0,0 +1,9 @@ +package com.susu.domain.repository + +import com.susu.core.model.Token +import com.susu.core.model.User + +interface SignUpRepository { + suspend fun signUp(oauthAccessToken: String, user: User): Token + suspend fun canRegister(oauthAccessToken: String): Boolean +} From 722b085a1d84570b750b3d3fde9f43e0e96fa4eb Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 13:10:18 +0900 Subject: [PATCH 52/60] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8/?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Usecase=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/RepositoryModule.kt | 17 +++-- .../domain/usecase/CheckCanRegisterUseCase.kt | 13 ++++ .../com/susu/domain/usecase/LoginUseCase.kt | 17 +++++ .../com/susu/domain/usecase/SignUpUseCase.kt | 21 ++++++ .../loginsignup/login/LoginViewModel.kt | 67 +++++-------------- .../loginsignup/signup/SignUpViewModel.kt | 18 +++-- .../feature/loginsignup/test/TestViewModel.kt | 9 +-- 7 files changed, 92 insertions(+), 70 deletions(-) create mode 100644 domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt create mode 100644 domain/src/main/java/com/susu/domain/usecase/LoginUseCase.kt create mode 100644 domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt diff --git a/data/src/main/java/com/susu/data/di/RepositoryModule.kt b/data/src/main/java/com/susu/data/di/RepositoryModule.kt index be606017..07c4929f 100644 --- a/data/src/main/java/com/susu/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/susu/data/di/RepositoryModule.kt @@ -1,8 +1,10 @@ package com.susu.data.di -import com.susu.data.repository.AuthRepositoryImpl +import com.susu.data.repository.LoginRepositoryImpl +import com.susu.data.repository.SignUpRepositoryImpl import com.susu.data.repository.TokenRepositoryImpl -import com.susu.domain.repository.AuthRepository +import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.SignUpRepository import com.susu.domain.repository.TokenRepository import dagger.Binds import dagger.Module @@ -19,7 +21,12 @@ abstract class RepositoryModule { ): TokenRepository @Binds - abstract fun bindAuthRepository( - authRepositoryImpl: AuthRepositoryImpl, - ): AuthRepository + abstract fun bindSignUpRepository( + signUpRepositoryImpl: SignUpRepositoryImpl, + ): SignUpRepository + + @Binds + abstract fun bindLoginRepository( + loginRepositoryImpl: LoginRepositoryImpl, + ): LoginRepository } diff --git a/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt new file mode 100644 index 00000000..8f189a6d --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt @@ -0,0 +1,13 @@ +package com.susu.domain.usecase + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.SignUpRepository +import javax.inject.Inject + +class CheckCanRegisterUseCase @Inject constructor( + private val signUpRepository: SignUpRepository +) { + suspend operator fun invoke(oauthAccessToken: String) = runCatchingIgnoreCancelled { + signUpRepository.canRegister(oauthAccessToken = oauthAccessToken) + } +} diff --git a/domain/src/main/java/com/susu/domain/usecase/LoginUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/LoginUseCase.kt new file mode 100644 index 00000000..a861da21 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/LoginUseCase.kt @@ -0,0 +1,17 @@ +package com.susu.domain.usecase + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.TokenRepository +import javax.inject.Inject + +class LoginUseCase @Inject constructor( + private val loginRepository: LoginRepository, + private val tokenRepository: TokenRepository, +) { + suspend operator fun invoke(oauthAccessToken: String) = runCatchingIgnoreCancelled { + tokenRepository.saveTokens( + token = loginRepository.login(oauthAccessToken = oauthAccessToken), + ) + } +} diff --git a/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt new file mode 100644 index 00000000..1507f990 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt @@ -0,0 +1,21 @@ +package com.susu.domain.usecase + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.core.model.User +import com.susu.domain.repository.SignUpRepository +import com.susu.domain.repository.TokenRepository +import javax.inject.Inject + +class SignUpUseCase @Inject constructor( + private val signUpRepository: SignUpRepository, + private val tokenRepository: TokenRepository, +) { + suspend operator fun invoke( + oauthAccessToken: String, + user: User + ) = runCatchingIgnoreCancelled { + tokenRepository.saveTokens( + token = signUpRepository.signUp(oauthAccessToken = oauthAccessToken, user = user) + ) + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index d4d37c10..d2a6fac1 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -2,68 +2,33 @@ package com.susu.feature.loginsignup.login import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel -import com.susu.domain.repository.AuthRepository -import com.susu.domain.repository.TokenRepository -import com.susu.feature.loginsignup.social.KakaoLoginHelper +import com.susu.domain.usecase.CheckCanRegisterUseCase +import com.susu.domain.usecase.LoginUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import timber.log.Timber import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val authRepository: AuthRepository, - private val tokenRepository: TokenRepository, + private val checkCanRegisterUseCase: CheckCanRegisterUseCase, + private val loginUseCase: LoginUseCase, ) : BaseViewModel(LoginContract.LoginState()) { - init { - viewModelScope.launch { - intent { copy(isLoading = true) } - Timber.tag("AUTH").d("카카오 로그인 이력 확인") - // 1. 카카오 토큰 존재 여부 - if (!KakaoLoginHelper.hasKakaoLoginHistory()) { - // 1-1. 신규 유저 - intent { copy(isLoading = false, showVote = true) } - Timber.tag("AUTH").d("신규유져") - } else { - // 2. 카카오 access token 존재 시 로그인 시도 - Timber.tag("AUTH").d("수수 로그인 시도") - KakaoLoginHelper.getAccessToken { - it?.let { accessToken -> - viewModelScope.launch { - authRepository.login(accessToken).onSuccess { token -> - Timber.tag("AUTH").d("수수 로그인 성공") - runBlocking { - tokenRepository.saveTokens(token) - } - postSideEffect(LoginContract.LoginEffect.NavigateToReceived) - } - intent { copy(isLoading = false) } - } - } - } - } - } - } - - fun login(accessToken: String) { + fun login(oauthAccessToken: String) { viewModelScope.launch { intent { copy(isLoading = true) } // 수수 서버에 가입되지 않은 회원이라면 -> 회원 정보 기입 후 수수 회원가입 - if (authRepository.canRegister(accessToken)) { - Timber.tag("AUTH").d("수수 가입 가능") - postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) - } else { - authRepository.login(accessToken).onSuccess { token -> - Timber.tag("AUTH").d("수수 로그인 성공 ${token.accessToken}") - runBlocking { - tokenRepository.saveTokens(token) - } - postSideEffect(LoginContract.LoginEffect.NavigateToReceived) - }.onFailure { - Timber.tag("AUTH").d("수수 로그인 실패 ${it.message}") - postSideEffect(LoginContract.LoginEffect.ShowToast(it.message ?: "수수 로그인 실패")) + checkCanRegisterUseCase(oauthAccessToken = oauthAccessToken).onSuccess { canRegister -> + if (canRegister) { + postSideEffect(LoginContract.LoginEffect.NavigateToSignUp) + } else { + loginUseCase(oauthAccessToken = oauthAccessToken) + .onSuccess { + postSideEffect(LoginContract.LoginEffect.NavigateToReceived) + } + .onFailure { + postSideEffect(LoginContract.LoginEffect.ShowToast(it.message ?: "수수 로그인 실패")) + } } } intent { copy(isLoading = false) } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt index ed9e1c24..4391feed 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/signup/SignUpViewModel.kt @@ -3,18 +3,15 @@ package com.susu.feature.loginsignup.signup import androidx.lifecycle.viewModelScope import com.susu.core.model.User import com.susu.core.ui.base.BaseViewModel -import com.susu.domain.repository.AuthRepository -import com.susu.domain.repository.TokenRepository +import com.susu.domain.usecase.SignUpUseCase import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import javax.inject.Inject @HiltViewModel class SignUpViewModel @Inject constructor( - private val authRepository: AuthRepository, - private val tokenRepository: TokenRepository, + private val signUpUseCase: SignUpUseCase, ) : BaseViewModel(SignUpContract.SignUpState()) { fun updateName(name: String) { @@ -30,22 +27,23 @@ class SignUpViewModel @Inject constructor( } fun signUp() { - KakaoLoginHelper.getAccessToken { + KakaoLoginHelper.getAccessToken { oauthAccessToken -> viewModelScope.launch { - if (it != null) { - authRepository.signUp( - oauthAccessToken = it, + if (oauthAccessToken != null) { + signUpUseCase( + oauthAccessToken = oauthAccessToken, user = User( name = uiState.value.name, gender = uiState.value.gender, birth = uiState.value.birth.toInt(), ), ).onSuccess { - runBlocking { tokenRepository.saveTokens(it) } postSideEffect(SignUpContract.SignUpEffect.NavigateToReceived) }.onFailure { postSideEffect(SignUpContract.SignUpEffect.ShowToast(it.message ?: "에러 발생")) } + } else { + postSideEffect(SignUpContract.SignUpEffect.ShowToast("카카오톡 로그인 에러 발생")) } } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt index 27286b78..991db4b4 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt @@ -2,7 +2,8 @@ package com.susu.feature.loginsignup.test import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel -import com.susu.domain.repository.AuthRepository +import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.SignUpRepository import com.susu.domain.repository.TokenRepository import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel @@ -13,12 +14,12 @@ import javax.inject.Inject // 마이페이지에 들어갈 기능입니다. @HiltViewModel class TestViewModel @Inject constructor( - private val authRepository: AuthRepository, + private val loginRepository: LoginRepository, private val tokenRepository: TokenRepository, ) : BaseViewModel(TestContract.TestState) { fun logout() { viewModelScope.launch { - authRepository.logout() + loginRepository.logout() KakaoLoginHelper.logout() tokenRepository.deleteTokens() } @@ -28,7 +29,7 @@ class TestViewModel @Inject constructor( fun withdraw() { KakaoLoginHelper.unlink().onSuccess { viewModelScope.launch { - runBlocking { authRepository.withdraw() } + runBlocking { loginRepository.withdraw() } tokenRepository.deleteTokens() } } From 60675d543e9d296e6f202ef0f5b8ac3bc1e178db Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 13:37:10 +0900 Subject: [PATCH 53/60] =?UTF-8?q?feat:=20SplashScreen=20Api=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/navigator/build.gradle.kts | 2 ++ feature/navigator/src/main/AndroidManifest.xml | 2 +- .../main/java/com/susu/feature/navigator/MainActivity.kt | 2 ++ feature/navigator/src/main/res/values/themes.xml | 8 ++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 feature/navigator/src/main/res/values/themes.xml diff --git a/feature/navigator/build.gradle.kts b/feature/navigator/build.gradle.kts index e3fad03d..c6c336c0 100644 --- a/feature/navigator/build.gradle.kts +++ b/feature/navigator/build.gradle.kts @@ -14,4 +14,6 @@ dependencies { implementation(projects.feature.received) implementation(projects.feature.sent) implementation(projects.feature.statistics) + + implementation(libs.androidx.splashscreen) } diff --git a/feature/navigator/src/main/AndroidManifest.xml b/feature/navigator/src/main/AndroidManifest.xml index a8e9bc3c..65b22ea9 100644 --- a/feature/navigator/src/main/AndroidManifest.xml +++ b/feature/navigator/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt index 4ce86a28..9a567788 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.ui.Modifier +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import com.susu.core.designsystem.theme.SusuTheme import dagger.hilt.android.AndroidEntryPoint @@ -17,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) diff --git a/feature/navigator/src/main/res/values/themes.xml b/feature/navigator/src/main/res/values/themes.xml new file mode 100644 index 00000000..6a6df982 --- /dev/null +++ b/feature/navigator/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + + From 5f5b40e7a645b20605f5bd7766b3f84f4a115208 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 13:52:58 +0900 Subject: [PATCH 54/60] =?UTF-8?q?feat:=20=EC=95=B1=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9E=91=EC=97=85=20=EC=A4=91?= =?UTF-8?q?=EC=97=90=20SplashScreen=EC=9D=84=20=EA=B3=84=EC=86=8D=20?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/feature/navigator/MainActivity.kt | 32 +++++++++++++++++++ .../navigator/initialization/InitialRoute.kt | 5 +++ .../navigator/initialization/MainContract.kt | 12 +++++++ .../navigator/initialization/MainViewModel.kt | 8 +++++ 4 files changed, 57 insertions(+) create mode 100644 feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt create mode 100644 feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt create mode 100644 feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt index 9a567788..22afb869 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt @@ -3,24 +3,56 @@ package com.susu.feature.navigator import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.viewModels import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.susu.core.designsystem.theme.SusuTheme +import com.susu.feature.navigator.initialization.MainContract +import com.susu.feature.navigator.initialization.MainViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch @AndroidEntryPoint class MainActivity : ComponentActivity() { + + private val viewModel: MainViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) + var uiState: MainContract.MainState by mutableStateOf(MainContract.MainState.Loading) + + // Update the uiState + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { + uiState = it + } + } + } + + splashScreen.setKeepOnScreenCondition { + when (uiState) { + MainContract.MainState.Loading -> true + is MainContract.MainState.Initialized -> false + } + } + WindowCompat.setDecorFitsSystemWindows(window, false) setContent { diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt new file mode 100644 index 00000000..99c32429 --- /dev/null +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt @@ -0,0 +1,5 @@ +package com.susu.feature.navigator.initialization + +enum class InitialRoute { + SIGNUP_VOTE, LOGIN, RECEIVED +} diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt new file mode 100644 index 00000000..61c6662b --- /dev/null +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt @@ -0,0 +1,12 @@ +package com.susu.feature.navigator.initialization + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +sealed interface MainContract { + object MainEffect : SideEffect + sealed class MainState : UiState { + data object Loading : MainState() + data class Initialized(val initialRoute: InitialRoute) : MainState() + } +} diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt new file mode 100644 index 00000000..2cad8a43 --- /dev/null +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt @@ -0,0 +1,8 @@ +package com.susu.feature.navigator.initialization + +import com.susu.core.ui.base.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor() : BaseViewModel(MainContract.MainState.Loading) From e21b8a0029a516a29128da70ea1f11462a3015ec Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 14:50:09 +0900 Subject: [PATCH 55/60] =?UTF-8?q?feat:=20Splash=EB=A5=BC=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EA=B3=A0=20=EB=82=9C=20=EB=92=A4,=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=ED=99=94=EB=A9=B4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginsignup/social/KakaoLoginHelper.kt | 2 +- .../susu/feature/navigator/MainActivity.kt | 37 +++++++++++-------- .../susu/feature/navigator/MainNavigator.kt | 3 -- .../com/susu/feature/navigator/MainScreen.kt | 5 +-- .../navigator/initialization/InitialRoute.kt | 2 +- .../navigator/initialization/MainContract.kt | 8 ++-- .../navigator/initialization/MainViewModel.kt | 29 ++++++++++++++- 7 files changed, 57 insertions(+), 29 deletions(-) diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt index a541e466..1a33abd1 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt @@ -9,7 +9,7 @@ import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient import timber.log.Timber -internal object KakaoLoginHelper { +object KakaoLoginHelper { fun login( context: Context, diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt index 22afb869..0f1f079c 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt @@ -20,10 +20,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.susu.core.designsystem.theme.SusuTheme +import com.susu.feature.loginsignup.navigation.LoginSignupRoute +import com.susu.feature.navigator.initialization.InitialRoute import com.susu.feature.navigator.initialization.MainContract import com.susu.feature.navigator.initialization.MainViewModel +import com.susu.feature.received.navigation.ReceivedRoute import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @AndroidEntryPoint @@ -35,7 +37,7 @@ class MainActivity : ComponentActivity() { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) - var uiState: MainContract.MainState by mutableStateOf(MainContract.MainState.Loading) + var uiState: MainContract.MainState by mutableStateOf(MainContract.MainState()) // Update the uiState lifecycleScope.launch { @@ -46,26 +48,29 @@ class MainActivity : ComponentActivity() { } } - splashScreen.setKeepOnScreenCondition { - when (uiState) { - MainContract.MainState.Loading -> true - is MainContract.MainState.Initialized -> false - } - } + splashScreen.setKeepOnScreenCondition { uiState.isLoading } WindowCompat.setDecorFitsSystemWindows(window, false) setContent { SusuTheme { - MainScreen( - modifier = Modifier - .fillMaxSize() - .windowInsetsPadding( - WindowInsets.systemBars.only( - WindowInsetsSides.Vertical, + if (uiState.isLoading.not()) { + MainScreen( + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding( + WindowInsets.systemBars.only( + WindowInsetsSides.Vertical, + ), ), - ), - ) + startDestination = when (uiState.initialRoute) { + InitialRoute.SIGNUP_VOTE -> LoginSignupRoute.Parent.route + InitialRoute.LOGIN -> LoginSignupRoute.Parent.route + InitialRoute.RECEIVED -> ReceivedRoute.route + InitialRoute.NONE -> LoginSignupRoute.Parent.route + }, + ) + } } } } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt index d7a54282..ab742b73 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainNavigator.kt @@ -9,7 +9,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.susu.feature.community.navigation.navigateCommunity -import com.susu.feature.loginsignup.navigation.LoginSignupRoute import com.susu.feature.mypage.navigation.navigateMyPage import com.susu.feature.received.navigation.navigateReceived import com.susu.feature.sent.navigation.SentRoute @@ -23,8 +22,6 @@ internal class MainNavigator( @Composable get() = navController .currentBackStackEntryAsState().value?.destination - val startDestination = LoginSignupRoute.Parent.route - val currentTab: MainNavigationTab? @Composable get() = currentDestination ?.route diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt index 6a7fa5bb..25c86334 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt @@ -7,8 +7,6 @@ import androidx.compose.animation.slideIn import androidx.compose.animation.slideOut import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset @@ -27,6 +25,7 @@ import kotlinx.collections.immutable.toImmutableList @Composable internal fun MainScreen( modifier: Modifier = Modifier, + startDestination: String, navigator: MainNavigator = rememberMainNavigator(), ) { Scaffold( @@ -34,7 +33,7 @@ internal fun MainScreen( content = { innerPadding -> NavHost( navController = navigator.navController, - startDestination = navigator.startDestination, + startDestination = startDestination, ) { loginSignupNavGraph(navigator.navController) diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt index 99c32429..1e3abd51 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt @@ -1,5 +1,5 @@ package com.susu.feature.navigator.initialization enum class InitialRoute { - SIGNUP_VOTE, LOGIN, RECEIVED + SIGNUP_VOTE, LOGIN, RECEIVED, NONE } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt index 61c6662b..c21df4f3 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt @@ -5,8 +5,8 @@ import com.susu.core.ui.base.UiState sealed interface MainContract { object MainEffect : SideEffect - sealed class MainState : UiState { - data object Loading : MainState() - data class Initialized(val initialRoute: InitialRoute) : MainState() - } + data class MainState( + val isLoading: Boolean = true, + val initialRoute: InitialRoute = InitialRoute.NONE + ) : UiState } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt index 2cad8a43..6fe6131e 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt @@ -1,8 +1,35 @@ package com.susu.feature.navigator.initialization +import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.usecase.LoginUseCase +import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class MainViewModel @Inject constructor() : BaseViewModel(MainContract.MainState.Loading) +class MainViewModel @Inject constructor( + private val loginUseCase: LoginUseCase, +) : BaseViewModel(MainContract.MainState()) { + init { + if (!KakaoLoginHelper.hasKakaoLoginHistory()) { + intent { copy(isLoading = false, initialRoute = InitialRoute.SIGNUP_VOTE) } + } else { + KakaoLoginHelper.getAccessToken { oauthAccessToken -> + if (oauthAccessToken == null) { + intent { copy(isLoading = false, initialRoute = InitialRoute.LOGIN) } + } else { + viewModelScope.launch { + loginUseCase(oauthAccessToken = oauthAccessToken) + .onSuccess { + intent { copy(isLoading = false, initialRoute = InitialRoute.RECEIVED) } + }.onFailure { + intent { copy(isLoading = false, initialRoute = InitialRoute.LOGIN) } + } + } + } + } + } + } +} From 8928f592c868e2789615cae20202d43c9efbaa83 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 15:25:55 +0900 Subject: [PATCH 56/60] =?UTF-8?q?refactor:=20TestScreen=EC=9D=98=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=EC=9D=84=20MyPage=EB=A1=9C=20=EC=98=AE?= =?UTF-8?q?=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/susu/data/di/NetworkModule.kt | 1 - .../com/susu/domain/usecase/LogoutUseCase.kt | 16 +++++++ .../susu/domain/usecase/WithdrawUseCase.kt | 16 +++++++ .../susu/feature/loginsignup/VoteScreen.kt | 14 ++++++ .../navigation/LoginSignupNavigation.kt | 42 ++++++++--------- .../loginsignup/social/KakaoLoginHelper.kt | 19 +------- .../feature/loginsignup/test/TestContract.kt | 12 ----- .../feature/loginsignup/test/TestScreen.kt | 33 -------------- .../feature/loginsignup/test/TestViewModel.kt | 38 ---------------- .../com/susu/feature/mypage/MyPageContract.kt | 13 ++++++ .../com/susu/feature/mypage/MyPageScreen.kt | 34 +++++++++++--- .../susu/feature/mypage/MyPageViewModel.kt | 45 +++++++++++++++++++ .../mypage/navigation/MyPageNavigation.kt | 3 +- .../susu/feature/navigator/MainActivity.kt | 4 +- .../com/susu/feature/navigator/MainScreen.kt | 11 ++++- 15 files changed, 169 insertions(+), 132 deletions(-) create mode 100644 domain/src/main/java/com/susu/domain/usecase/LogoutUseCase.kt create mode 100644 domain/src/main/java/com/susu/domain/usecase/WithdrawUseCase.kt create mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt delete mode 100644 feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt create mode 100644 feature/mypage/src/main/java/com/susu/feature/mypage/MyPageContract.kt create mode 100644 feature/mypage/src/main/java/com/susu/feature/mypage/MyPageViewModel.kt diff --git a/data/src/main/java/com/susu/data/di/NetworkModule.kt b/data/src/main/java/com/susu/data/di/NetworkModule.kt index 3541ac94..dd797db8 100644 --- a/data/src/main/java/com/susu/data/di/NetworkModule.kt +++ b/data/src/main/java/com/susu/data/di/NetworkModule.kt @@ -72,7 +72,6 @@ object NetworkModule { @AuthOkHttpClient fun provideAuthOkHttpClient( loggingInterceptor: HttpLoggingInterceptor, - json: Json, ): OkHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build() diff --git a/domain/src/main/java/com/susu/domain/usecase/LogoutUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/LogoutUseCase.kt new file mode 100644 index 00000000..62817032 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/LogoutUseCase.kt @@ -0,0 +1,16 @@ +package com.susu.domain.usecase + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.TokenRepository +import javax.inject.Inject + +class LogoutUseCase @Inject constructor( + private val loginRepository: LoginRepository, + private val tokenRepository: TokenRepository, +) { + suspend operator fun invoke() = runCatchingIgnoreCancelled { + loginRepository.logout() + tokenRepository.deleteTokens() + } +} diff --git a/domain/src/main/java/com/susu/domain/usecase/WithdrawUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/WithdrawUseCase.kt new file mode 100644 index 00000000..77d9c355 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/WithdrawUseCase.kt @@ -0,0 +1,16 @@ +package com.susu.domain.usecase + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.TokenRepository +import javax.inject.Inject + +class WithdrawUseCase @Inject constructor( + private val loginRepository: LoginRepository, + private val tokenRepository: TokenRepository, +) { + suspend operator fun invoke() = runCatchingIgnoreCancelled { + loginRepository.withdraw() + tokenRepository.deleteTokens() + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt new file mode 100644 index 00000000..d0bed1d9 --- /dev/null +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt @@ -0,0 +1,14 @@ +package com.susu.feature.loginsignup + +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + +@Composable +fun VoteScreen( + navigateToLogin: () -> Unit +) { + Button(onClick = navigateToLogin) { + Text("대충 회원가입 투표") + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt index 281be13b..efc02902 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/navigation/LoginSignupNavigation.kt @@ -4,41 +4,41 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import androidx.navigation.navigation +import com.susu.feature.loginsignup.VoteScreen import com.susu.feature.loginsignup.login.LoginScreen import com.susu.feature.loginsignup.signup.SignUpScreen -import com.susu.feature.loginsignup.test.TestScreen @Suppress("unused") fun NavController.navigateLoginSignup(navOptions: NavOptions) { navigate(LoginSignupRoute.Parent.route, navOptions) } -fun NavGraphBuilder.loginSignupNavGraph(navController: NavController) { - navigation(startDestination = LoginSignupRoute.Parent.Login.route, route = LoginSignupRoute.Parent.route) { - composable(route = LoginSignupRoute.Parent.Login.route) { - LoginScreen( - navigateToReceived = { navController.navigate(LoginSignupRoute.Parent.Test.route) }, - navigateToSignUp = { navController.navigate(LoginSignupRoute.Parent.SignUp.route) }, - ) - } - composable(route = LoginSignupRoute.Parent.SignUp.route) { - SignUpScreen( - navigateToReceived = { navController.navigate(LoginSignupRoute.Parent.Test.route) }, - ) - } - composable(route = LoginSignupRoute.Parent.Test.route) { - TestScreen( - navigateToLogin = { navController.popBackStack() }, - ) - } +fun NavGraphBuilder.loginSignupNavGraph( + navController: NavController, + navigateToReceived: () -> Unit, +) { + composable(route = LoginSignupRoute.Parent.Vote.route) { + VoteScreen( + navigateToLogin = { navController.navigate(LoginSignupRoute.Parent.Login.route) }, + ) + } + composable(route = LoginSignupRoute.Parent.Login.route) { + LoginScreen( + navigateToReceived = navigateToReceived, + navigateToSignUp = { navController.navigate(LoginSignupRoute.Parent.SignUp.route) }, + ) + } + composable(route = LoginSignupRoute.Parent.SignUp.route) { + SignUpScreen( + navigateToReceived = navigateToReceived, + ) } } sealed class LoginSignupRoute(val route: String) { data object Parent : LoginSignupRoute("login-signup") { + data object Vote : LoginSignupRoute("vote") data object Login : LoginSignupRoute("login") data object SignUp : LoginSignupRoute("signup") - data object Test : LoginSignupRoute("test") } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt index 1a33abd1..2856c2eb 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/social/KakaoLoginHelper.kt @@ -26,7 +26,7 @@ object KakaoLoginHelper { loginWithKakaoAccount( onSuccess = onSuccess, onFailed = onFailed, - context = context + context = context, ) } }, @@ -87,21 +87,4 @@ object KakaoLoginHelper { } } } - - // 기능 테스트를 위함. - fun logout() = runCatching { - UserApiClient.instance.logout { error -> - if (error != null) { - throw error - } - } - } - - fun unlink() = runCatching { - UserApiClient.instance.unlink { error -> - if (error != null) { - throw error - } - } - } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt deleted file mode 100644 index 2136d5e7..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestContract.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.susu.feature.loginsignup.test - -import com.susu.core.ui.base.SideEffect -import com.susu.core.ui.base.UiState - -sealed interface TestContract { - sealed class TestEffect : SideEffect { - data object NavigateToLogin : TestEffect() - } - - object TestState : UiState -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt deleted file mode 100644 index cbda00b6..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestScreen.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.susu.feature.loginsignup.test - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.hilt.navigation.compose.hiltViewModel - -@Composable -fun TestScreen( - viewModel: TestViewModel = hiltViewModel(), - navigateToLogin: () -> Unit, -) { - LaunchedEffect(key1 = viewModel.sideEffect) { - viewModel.sideEffect.collect { sideEffect -> - when (sideEffect) { - TestContract.TestEffect.NavigateToLogin -> navigateToLogin() - } - } - } - - Column { - Button(onClick = viewModel::logout) { - Text(text = "로그아웃") - } - Button( - onClick = viewModel::withdraw, - ) { - Text(text = "탈퇴") - } - } -} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt deleted file mode 100644 index 991db4b4..00000000 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/test/TestViewModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.susu.feature.loginsignup.test - -import androidx.lifecycle.viewModelScope -import com.susu.core.ui.base.BaseViewModel -import com.susu.domain.repository.LoginRepository -import com.susu.domain.repository.SignUpRepository -import com.susu.domain.repository.TokenRepository -import com.susu.feature.loginsignup.social.KakaoLoginHelper -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import javax.inject.Inject - -// 마이페이지에 들어갈 기능입니다. -@HiltViewModel -class TestViewModel @Inject constructor( - private val loginRepository: LoginRepository, - private val tokenRepository: TokenRepository, -) : BaseViewModel(TestContract.TestState) { - fun logout() { - viewModelScope.launch { - loginRepository.logout() - KakaoLoginHelper.logout() - tokenRepository.deleteTokens() - } - postSideEffect(TestContract.TestEffect.NavigateToLogin) - } - - fun withdraw() { - KakaoLoginHelper.unlink().onSuccess { - viewModelScope.launch { - runBlocking { loginRepository.withdraw() } - tokenRepository.deleteTokens() - } - } - postSideEffect(TestContract.TestEffect.NavigateToLogin) - } -} diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageContract.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageContract.kt new file mode 100644 index 00000000..b66aa66b --- /dev/null +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageContract.kt @@ -0,0 +1,13 @@ +package com.susu.feature.mypage + +import com.susu.core.ui.base.SideEffect +import com.susu.core.ui.base.UiState + +interface MyPageContract { + sealed class MyPageEffect : SideEffect { + data object NavigateToLogin : MyPageEffect() + data class ShowToast(val msg: String) : MyPageEffect() + } + + object MyPageState : UiState +} diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt index 95aac460..ca19fa4b 100644 --- a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt @@ -1,28 +1,52 @@ package com.susu.feature.mypage +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import com.susu.core.designsystem.theme.SusuTheme @Composable fun MyPageScreen( padding: PaddingValues, + viewModel: MyPageViewModel = hiltViewModel(), + navigateToLogin: () -> Unit, ) { - Text( - modifier = Modifier.padding(padding), - text = "마이 페이지", - ) + LaunchedEffect(key1 = viewModel.sideEffect) { + viewModel.sideEffect.collect { sideEffect -> + when (sideEffect) { + MyPageContract.MyPageEffect.NavigateToLogin -> navigateToLogin() + is MyPageContract.MyPageEffect.ShowToast -> { + //TODO: UI 작업 시 에러 메세지 표시 + navigateToLogin() + } + } + } + } + + Column { + Button(onClick = viewModel::logout) { + Text(text = "로그아웃") + } + Button( + onClick = viewModel::withdraw, + ) { + Text(text = "탈퇴") + } + } } @Preview @Composable fun MyPageScreenPreview() { SusuTheme { - MyPageScreen(padding = PaddingValues(0.dp)) + MyPageScreen(padding = PaddingValues(0.dp)) {} } } diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageViewModel.kt new file mode 100644 index 00000000..f3ac1a97 --- /dev/null +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageViewModel.kt @@ -0,0 +1,45 @@ +package com.susu.feature.mypage + +import androidx.lifecycle.viewModelScope +import com.kakao.sdk.user.UserApiClient +import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.usecase.LogoutUseCase +import com.susu.domain.usecase.WithdrawUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyPageViewModel @Inject constructor( + private val logoutUseCase: LogoutUseCase, + private val withdrawUseCase: WithdrawUseCase, +) : BaseViewModel(MyPageContract.MyPageState) { + + fun logout() { + UserApiClient.instance.logout { error -> + if (error != null) { + postSideEffect(MyPageContract.MyPageEffect.ShowToast(error.message ?: "에러 발생했지만 토큰은 삭제됨")) + } else { + viewModelScope.launch { + logoutUseCase().onSuccess { + postSideEffect(MyPageContract.MyPageEffect.NavigateToLogin) + } + } + } + } + } + + fun withdraw() { + UserApiClient.instance.unlink { error -> + if (error != null) { + postSideEffect(MyPageContract.MyPageEffect.ShowToast(error.message ?: "에러 발생했지만 토큰은 삭제됨")) + } else { + viewModelScope.launch { + withdrawUseCase().onSuccess { + postSideEffect(MyPageContract.MyPageEffect.NavigateToLogin) + } + } + } + } + } +} diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt index c5f5b0ca..4767c9f7 100644 --- a/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt @@ -13,9 +13,10 @@ fun NavController.navigateMyPage(navOptions: NavOptions) { fun NavGraphBuilder.myPageNavGraph( padding: PaddingValues, + navigateToLogin: () -> Unit ) { composable(route = MyPageRoute.route) { - MyPageScreen(padding) + MyPageScreen(padding, navigateToLogin = navigateToLogin) } } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt index 0f1f079c..189f0e1a 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt @@ -64,8 +64,8 @@ class MainActivity : ComponentActivity() { ), ), startDestination = when (uiState.initialRoute) { - InitialRoute.SIGNUP_VOTE -> LoginSignupRoute.Parent.route - InitialRoute.LOGIN -> LoginSignupRoute.Parent.route + InitialRoute.SIGNUP_VOTE -> LoginSignupRoute.Parent.Vote.route + InitialRoute.LOGIN -> LoginSignupRoute.Parent.Login.route InitialRoute.RECEIVED -> ReceivedRoute.route InitialRoute.NONE -> LoginSignupRoute.Parent.route }, diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt index 25c86334..e246635a 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainScreen.kt @@ -14,6 +14,7 @@ import androidx.navigation.compose.NavHost import com.susu.core.designsystem.component.navigation.SusuNavigationBar import com.susu.core.designsystem.component.navigation.SusuNavigationItem import com.susu.feature.community.navigation.communityNavGraph +import com.susu.feature.loginsignup.navigation.LoginSignupRoute import com.susu.feature.loginsignup.navigation.loginSignupNavGraph import com.susu.feature.mypage.navigation.myPageNavGraph import com.susu.feature.received.navigation.receivedNavGraph @@ -35,7 +36,14 @@ internal fun MainScreen( navController = navigator.navController, startDestination = startDestination, ) { - loginSignupNavGraph(navigator.navController) + loginSignupNavGraph( + navController = navigator.navController, + navigateToReceived = { + // TODO: 이쪽으로 수정 + // navigator.navController.navigateReceived(navOptions = NavOptions()) + navigator.navigate(MainNavigationTab.RECEIVED) + }, + ) sentNavGraph( padding = innerPadding, @@ -55,6 +63,7 @@ internal fun MainScreen( myPageNavGraph( padding = innerPadding, + navigateToLogin = { navigator.navController.navigate(LoginSignupRoute.Parent.Login.route) }, ) } }, From 846ade5c14a6824c197acbc7c1e303bfad98b347 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 16:00:44 +0900 Subject: [PATCH 57/60] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9B=84=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=9D=B4=ED=83=88=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EB=8A=94=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/feature/navigator/MainActivity.kt | 3 +++ .../navigator/initialization/InitialRoute.kt | 2 +- .../navigator/initialization/MainViewModel.kt | 23 +++++++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt index 189f0e1a..42bf6583 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/MainActivity.kt @@ -27,6 +27,7 @@ import com.susu.feature.navigator.initialization.MainViewModel import com.susu.feature.received.navigation.ReceivedRoute import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import timber.log.Timber @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -44,6 +45,7 @@ class MainActivity : ComponentActivity() { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { uiState = it + Timber.d(uiState.toString()) } } } @@ -66,6 +68,7 @@ class MainActivity : ComponentActivity() { startDestination = when (uiState.initialRoute) { InitialRoute.SIGNUP_VOTE -> LoginSignupRoute.Parent.Vote.route InitialRoute.LOGIN -> LoginSignupRoute.Parent.Login.route + InitialRoute.SIGNUP -> LoginSignupRoute.Parent.SignUp.route InitialRoute.RECEIVED -> ReceivedRoute.route InitialRoute.NONE -> LoginSignupRoute.Parent.route }, diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt index 1e3abd51..b30a9aee 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/InitialRoute.kt @@ -1,5 +1,5 @@ package com.susu.feature.navigator.initialization enum class InitialRoute { - SIGNUP_VOTE, LOGIN, RECEIVED, NONE + SIGNUP_VOTE, LOGIN, SIGNUP, RECEIVED, NONE } diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt index 6fe6131e..72936208 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainViewModel.kt @@ -2,6 +2,7 @@ package com.susu.feature.navigator.initialization import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.usecase.CheckCanRegisterUseCase import com.susu.domain.usecase.LoginUseCase import com.susu.feature.loginsignup.social.KakaoLoginHelper import dagger.hilt.android.lifecycle.HiltViewModel @@ -11,6 +12,7 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( private val loginUseCase: LoginUseCase, + private val checkCanRegisterUseCase: CheckCanRegisterUseCase, ) : BaseViewModel(MainContract.MainState()) { init { if (!KakaoLoginHelper.hasKakaoLoginHistory()) { @@ -18,18 +20,29 @@ class MainViewModel @Inject constructor( } else { KakaoLoginHelper.getAccessToken { oauthAccessToken -> if (oauthAccessToken == null) { + // 웹에서 회원 탈퇴를 하고 앱에 접속했을 때 -> 카카오 로그인 화면으로 이동 intent { copy(isLoading = false, initialRoute = InitialRoute.LOGIN) } } else { viewModelScope.launch { - loginUseCase(oauthAccessToken = oauthAccessToken) - .onSuccess { - intent { copy(isLoading = false, initialRoute = InitialRoute.RECEIVED) } - }.onFailure { - intent { copy(isLoading = false, initialRoute = InitialRoute.LOGIN) } + checkCanRegisterUseCase(oauthAccessToken).onSuccess { canRegister -> + if (canRegister) { + intent { copy(isLoading = false, initialRoute = InitialRoute.SIGNUP) } + } else { + login(oauthAccessToken) } + } } } } } } + + private suspend fun login(oauthAccessToken: String) { + loginUseCase(oauthAccessToken = oauthAccessToken) + .onSuccess { + intent { copy(isLoading = false, initialRoute = InitialRoute.RECEIVED) } + }.onFailure { + intent { copy(isLoading = false, initialRoute = InitialRoute.LOGIN) } + } + } } From d4e1d00249592ce2d7f0da468ac3b9584029229a Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Tue, 2 Jan 2024 16:44:06 +0900 Subject: [PATCH 58/60] =?UTF-8?q?chore:=20detekt=EA=B0=80=20apiKey?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=AC=20=EB=95=8C=20null=20ch?= =?UTF-8?q?eck=20=EB=AC=B4=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bb78b824..c0f671ab 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(libs.timber) } +@Suppress fun getApiKey(propertyKey: String): String { return gradleLocalProperties(rootDir).getProperty(propertyKey) } From d0c87bbd65d83563cb301a18592fee30d8f3de38 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Wed, 3 Jan 2024 01:34:31 +0900 Subject: [PATCH 59/60] =?UTF-8?q?fix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9B=84=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 14 +++++++++++++- feature/loginsignup/build.gradle.kts | 9 --------- feature/loginsignup/src/main/AndroidManifest.xml | 14 +------------- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c0f671ab..b66e7b2c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,6 +16,7 @@ android { versionCode = 1 versionName = "1.0" buildConfigField("String", "KAKAO_APP_KEY", getApiKey("KAKAO_APP_KEY")) + manifestPlaceholders["KAKAO_APP_KEY"] = "kakao${getApiKey("KAKAO_REDIRECT_KEY")}" } buildFeatures { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9262901c..fd75a3d0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,18 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" tools:replace="android:allowBackup" - tools:targetApi="s"/> + tools:targetApi="s"> + + + + + + + + + diff --git a/feature/loginsignup/build.gradle.kts b/feature/loginsignup/build.gradle.kts index e779f278..0e8928cd 100644 --- a/feature/loginsignup/build.gradle.kts +++ b/feature/loginsignup/build.gradle.kts @@ -1,5 +1,3 @@ -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties - @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.susu.android.feature.compose) @@ -7,15 +5,8 @@ plugins { android { namespace = "com.susu.feature.loginsignup" - defaultConfig { - manifestPlaceholders["KAKAO_APP_KEY"] = "kakao${getApiKey("KAKAO_APP_KEY")}" - } } dependencies { implementation(libs.kakao.sdk.user) } - -fun getApiKey(propertyKey: String): String { - return gradleLocalProperties(rootDir).getProperty(propertyKey) -} diff --git a/feature/loginsignup/src/main/AndroidManifest.xml b/feature/loginsignup/src/main/AndroidManifest.xml index 82ad0534..9e8a98b2 100644 --- a/feature/loginsignup/src/main/AndroidManifest.xml +++ b/feature/loginsignup/src/main/AndroidManifest.xml @@ -1,17 +1,5 @@ - - - - - - - - - - + From d6f69acb3a75b443e35f1a05fab189de4f56d935 Mon Sep 17 00:00:00 2001 From: yangsooplus Date: Thu, 4 Jan 2024 02:26:01 +0900 Subject: [PATCH 60/60] =?UTF-8?q?chore:=20detekt=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- data/src/main/java/com/susu/data/di/ServiceModule.kt | 4 ++-- .../java/com/susu/data/network/service/SignUpService.kt | 2 +- .../main/java/com/susu/data/network/service/TokenService.kt | 2 +- .../main/java/com/susu/data/network/service/UserService.kt | 2 +- .../java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt | 2 +- .../src/main/java/com/susu/domain/usecase/SignUpUseCase.kt | 4 ++-- .../main/java/com/susu/feature/loginsignup/VoteScreen.kt | 2 +- .../src/main/java/com/susu/feature/mypage/MyPageScreen.kt | 6 ++++-- .../com/susu/feature/mypage/navigation/MyPageNavigation.kt | 2 +- .../susu/feature/navigator/initialization/MainContract.kt | 2 +- 11 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b66e7b2c..05c612d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,5 +38,5 @@ dependencies { @Suppress fun getApiKey(propertyKey: String): String { - return gradleLocalProperties(rootDir).getProperty(propertyKey) + return gradleLocalProperties(rootDir).getProperty(propertyKey) ?: "default" } diff --git a/data/src/main/java/com/susu/data/di/ServiceModule.kt b/data/src/main/java/com/susu/data/di/ServiceModule.kt index eb343916..1cdb22a4 100644 --- a/data/src/main/java/com/susu/data/di/ServiceModule.kt +++ b/data/src/main/java/com/susu/data/di/ServiceModule.kt @@ -1,8 +1,8 @@ package com.susu.data.di -import com.susu.data.network.service.UserService -import com.susu.data.network.service.TokenService import com.susu.data.network.service.SignUpService +import com.susu.data.network.service.TokenService +import com.susu.data.network.service.UserService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/src/main/java/com/susu/data/network/service/SignUpService.kt b/data/src/main/java/com/susu/data/network/service/SignUpService.kt index 44c717fc..32f9a9ff 100644 --- a/data/src/main/java/com/susu/data/network/service/SignUpService.kt +++ b/data/src/main/java/com/susu/data/network/service/SignUpService.kt @@ -1,7 +1,7 @@ package com.susu.data.network.service -import com.susu.data.model.response.TokenResponse import com.susu.data.model.request.UserRequest +import com.susu.data.model.response.TokenResponse import com.susu.data.model.response.ValidRegisterResponse import retrofit2.http.Body import retrofit2.http.GET diff --git a/data/src/main/java/com/susu/data/network/service/TokenService.kt b/data/src/main/java/com/susu/data/network/service/TokenService.kt index b3ba04a8..3f8a3a35 100644 --- a/data/src/main/java/com/susu/data/network/service/TokenService.kt +++ b/data/src/main/java/com/susu/data/network/service/TokenService.kt @@ -1,7 +1,7 @@ package com.susu.data.network.service -import com.susu.data.model.response.TokenResponse import com.susu.data.model.request.RefreshTokenRequest +import com.susu.data.model.response.TokenResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST diff --git a/data/src/main/java/com/susu/data/network/service/UserService.kt b/data/src/main/java/com/susu/data/network/service/UserService.kt index 7b147de2..87628a5e 100644 --- a/data/src/main/java/com/susu/data/network/service/UserService.kt +++ b/data/src/main/java/com/susu/data/network/service/UserService.kt @@ -1,7 +1,7 @@ package com.susu.data.network.service -import com.susu.data.model.response.TokenResponse import com.susu.data.model.request.AccessTokenRequest +import com.susu.data.model.response.TokenResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST diff --git a/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt index 8f189a6d..51f6cdcb 100644 --- a/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt +++ b/domain/src/main/java/com/susu/domain/usecase/CheckCanRegisterUseCase.kt @@ -5,7 +5,7 @@ import com.susu.domain.repository.SignUpRepository import javax.inject.Inject class CheckCanRegisterUseCase @Inject constructor( - private val signUpRepository: SignUpRepository + private val signUpRepository: SignUpRepository, ) { suspend operator fun invoke(oauthAccessToken: String) = runCatchingIgnoreCancelled { signUpRepository.canRegister(oauthAccessToken = oauthAccessToken) diff --git a/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt index 1507f990..64acbab2 100644 --- a/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt +++ b/domain/src/main/java/com/susu/domain/usecase/SignUpUseCase.kt @@ -12,10 +12,10 @@ class SignUpUseCase @Inject constructor( ) { suspend operator fun invoke( oauthAccessToken: String, - user: User + user: User, ) = runCatchingIgnoreCancelled { tokenRepository.saveTokens( - token = signUpRepository.signUp(oauthAccessToken = oauthAccessToken, user = user) + token = signUpRepository.signUp(oauthAccessToken = oauthAccessToken, user = user), ) } } diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt index d0bed1d9..2efe9e74 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/VoteScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable @Composable fun VoteScreen( - navigateToLogin: () -> Unit + navigateToLogin: () -> Unit, ) { Button(onClick = navigateToLogin) { Text("대충 회원가입 투표") diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt index ca19fa4b..a44cbee2 100644 --- a/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/MyPageScreen.kt @@ -24,14 +24,16 @@ fun MyPageScreen( when (sideEffect) { MyPageContract.MyPageEffect.NavigateToLogin -> navigateToLogin() is MyPageContract.MyPageEffect.ShowToast -> { - //TODO: UI 작업 시 에러 메세지 표시 + // TODO: UI 작업 시 에러 메세지 표시 navigateToLogin() } } } } - Column { + Column( + modifier = Modifier.padding(padding), + ) { Button(onClick = viewModel::logout) { Text(text = "로그아웃") } diff --git a/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt index 4767c9f7..cb28fa4b 100644 --- a/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/susu/feature/mypage/navigation/MyPageNavigation.kt @@ -13,7 +13,7 @@ fun NavController.navigateMyPage(navOptions: NavOptions) { fun NavGraphBuilder.myPageNavGraph( padding: PaddingValues, - navigateToLogin: () -> Unit + navigateToLogin: () -> Unit, ) { composable(route = MyPageRoute.route) { MyPageScreen(padding, navigateToLogin = navigateToLogin) diff --git a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt index c21df4f3..8e7dd616 100644 --- a/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt +++ b/feature/navigator/src/main/java/com/susu/feature/navigator/initialization/MainContract.kt @@ -7,6 +7,6 @@ sealed interface MainContract { object MainEffect : SideEffect data class MainState( val isLoading: Boolean = true, - val initialRoute: InitialRoute = InitialRoute.NONE + val initialRoute: InitialRoute = InitialRoute.NONE, ) : UiState }