diff --git a/core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt b/core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt new file mode 100644 index 00000000..b9e21547 --- /dev/null +++ b/core/network/src/main/java/com/umcspot/spot/network/TokenAuthenticator.kt @@ -0,0 +1,117 @@ +package com.umcspot.spot.network + +import androidx.datastore.core.DataStore +import com.umcspot.spot.datastore.token.SpotTokenData +import com.umcspot.spot.datastore.userId.SpotUserIdData +import com.umcspot.spot.network.service.TokenRefreshService +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import okhttp3.Authenticator +import okhttp3.Request +import okhttp3.Response +import okhttp3.Route +import javax.inject.Inject + +class TokenAuthenticator @Inject constructor( + private val spotTokenDataStore: DataStore, + private val spotUserIdDataStore: DataStore, + private val tokenRefreshService: TokenRefreshService +) : Authenticator { + + private val refreshLock = Any() + private object RefreshAttempted + + override fun authenticate(route: Route?, response: Response): Request? { + val refreshAttempted = response.request.tag(RefreshAttempted::class.java) != null + if (refreshAttempted) { + clearAuthData() + return null + } + if (responseCount(response) >= 2) return null + if (response.request.url.encodedPath.endsWith("/api/auth/reissue")) return null + + val requestAccessToken = response.request.header("Authorization") + ?.removePrefix("Bearer ") + .orEmpty() + val latestAccessToken = runBlocking { + spotTokenDataStore.data.first().accessToken + } + + if (latestAccessToken.isNotBlank() && latestAccessToken != requestAccessToken) { + return response.request.newBuilder() + .header("Authorization", "Bearer $latestAccessToken") + .tag(RefreshAttempted::class.java, RefreshAttempted) + .build() + } + + synchronized(refreshLock) { + val tokenData = runBlocking { spotTokenDataStore.data.first() } + if (tokenData.accessToken.isNotBlank() && tokenData.accessToken != requestAccessToken) { + return response.request.newBuilder() + .header("Authorization", "Bearer ${tokenData.accessToken}") + .tag(RefreshAttempted::class.java, RefreshAttempted) + .build() + } + + val refreshToken = tokenData.refreshToken + if (refreshToken.isBlank()) { + clearAuthData() + return null + } + + val refreshResponse = runBlocking { + try { + tokenRefreshService.refreshTokenData(refreshToken) + } catch (e: Exception) { + clearAuthData() + null + } + } + if (refreshResponse == null) return null + + if (!refreshResponse.isSuccess) { + clearAuthData() + return null + } + + val result = refreshResponse.result + runBlocking { + spotTokenDataStore.updateData { current -> + current.copy( + accessToken = result.accessToken, + refreshToken = result.refreshToken + ) + } + spotUserIdDataStore.updateData { current -> + current.copy(userId = result.userId) + } + } + + return response.request.newBuilder() + .header("Authorization", "Bearer ${result.accessToken}") + .tag(RefreshAttempted::class.java, RefreshAttempted) + .build() + } + } + + private fun clearAuthData() { + runBlocking { + spotTokenDataStore.updateData { current -> + current.copy(accessToken = "", refreshToken = "") + } + spotUserIdDataStore.updateData { current -> + current.copy(userId = "") + } + } + } + + private fun responseCount(response: Response): Int { + var count = 1 + var prior = response.priorResponse + while (prior != null) { + count++ + prior = prior.priorResponse + } + return count + } +} diff --git a/core/network/src/main/java/com/umcspot/spot/network/di/NetworkModule.kt b/core/network/src/main/java/com/umcspot/spot/network/di/NetworkModule.kt index a3a56bc7..00223a0a 100644 --- a/core/network/src/main/java/com/umcspot/spot/network/di/NetworkModule.kt +++ b/core/network/src/main/java/com/umcspot/spot/network/di/NetworkModule.kt @@ -4,6 +4,8 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.umcspot.spot.common.BuildConfigFieldProvider import com.umcspot.spot.common.WeatherConfigFieldProvider import com.umcspot.spot.network.AuthInterceptor +import com.umcspot.spot.network.TokenAuthenticator +import com.umcspot.spot.network.service.TokenRefreshService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -34,11 +36,23 @@ object NetworkModule { @SpotApi fun providesSpotOkHttpClient( loggingInterceptor: HttpLoggingInterceptor, - authInterceptor: AuthInterceptor + authInterceptor: AuthInterceptor, + tokenAuthenticator: TokenAuthenticator ): OkHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .addInterceptor(authInterceptor) + .authenticator(tokenAuthenticator) + .build() + + @Provides + @Singleton + @SpotRefreshApi + fun providesSpotRefreshOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, + ): OkHttpClient = + OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) .build() // ---------- Weather API ---------- @@ -74,7 +88,23 @@ object NetworkModule { .addConverterFactory(converterFactory) .build() - @Provides @Singleton @WeatherApi + @Provides + @Singleton + @SpotRefreshApi + fun providesSpotRefreshRetrofit( + @SpotRefreshApi client: OkHttpClient, + converterFactory: Converter.Factory, + buildConfigProvider: BuildConfigFieldProvider + ): Retrofit = + Retrofit.Builder() + .baseUrl(buildConfigProvider.get().baseUrl) + .client(client) + .addConverterFactory(converterFactory) + .build() + + @Provides + @Singleton + @WeatherApi fun providesWeatherRetrofit( @WeatherApi weatherClient: OkHttpClient, converterFactory: Converter.Factory, @@ -85,4 +115,11 @@ object NetworkModule { .client(weatherClient) .addConverterFactory(converterFactory) .build() -} \ No newline at end of file + + @Provides + @Singleton + fun providesTokenRefreshService( + @SpotRefreshApi retrofit: Retrofit + ): TokenRefreshService = + retrofit.create(TokenRefreshService::class.java) +} diff --git a/core/network/src/main/java/com/umcspot/spot/network/di/Qualifier.kt b/core/network/src/main/java/com/umcspot/spot/network/di/Qualifier.kt index df0dcd8a..1c6d0052 100644 --- a/core/network/src/main/java/com/umcspot/spot/network/di/Qualifier.kt +++ b/core/network/src/main/java/com/umcspot/spot/network/di/Qualifier.kt @@ -8,4 +8,8 @@ annotation class SpotApi @Qualifier @Retention(AnnotationRetention.BINARY) -annotation class WeatherApi \ No newline at end of file +annotation class WeatherApi + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class SpotRefreshApi diff --git a/core/network/src/main/java/com/umcspot/spot/network/model/TokenRefreshResponse.kt b/core/network/src/main/java/com/umcspot/spot/network/model/TokenRefreshResponse.kt new file mode 100644 index 00000000..872c9c84 --- /dev/null +++ b/core/network/src/main/java/com/umcspot/spot/network/model/TokenRefreshResponse.kt @@ -0,0 +1,14 @@ +package com.umcspot.spot.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TokenRefreshResponse( + @SerialName("id") + val userId: String, + @SerialName("accessToken") + val accessToken: String, + @SerialName("refreshToken") + val refreshToken: String +) diff --git a/core/network/src/main/java/com/umcspot/spot/network/service/TokenRefreshService.kt b/core/network/src/main/java/com/umcspot/spot/network/service/TokenRefreshService.kt index 6c0421ed..be2f5312 100644 --- a/core/network/src/main/java/com/umcspot/spot/network/service/TokenRefreshService.kt +++ b/core/network/src/main/java/com/umcspot/spot/network/service/TokenRefreshService.kt @@ -1,4 +1,15 @@ -package network.service +package com.umcspot.spot.network.service -class TokenRefreshService { + +import com.umcspot.spot.network.model.BaseResponse +import com.umcspot.spot.network.model.TokenRefreshResponse +import retrofit2.http.Header +import retrofit2.http.POST + +interface TokenRefreshService { + + @POST("/api/auth/reissue") + suspend fun refreshTokenData( + @Header("refreshToken") refreshToken: String, + ): BaseResponse } \ No newline at end of file diff --git a/data/login/src/main/java/com/umcspot/spot/login/datasource/LoginDataSource.kt b/data/login/src/main/java/com/umcspot/spot/login/datasource/LoginDataSource.kt index 79494c1e..f01bd6cb 100644 --- a/data/login/src/main/java/com/umcspot/spot/login/datasource/LoginDataSource.kt +++ b/data/login/src/main/java/com/umcspot/spot/login/datasource/LoginDataSource.kt @@ -1,10 +1,13 @@ package com.umcspot.spot.login.datasource import com.umcspot.spot.login.dto.response.TokenResponseDto -import com.umcspot.spot.model.SocialLoginType import com.umcspot.spot.network.model.BaseResponse +import com.umcspot.spot.network.model.NullResultResponse interface LoginDataSource { - suspend fun finishSocialLogin(type : String, accessToken : String): BaseResponse + suspend fun getCallBackToken(type : String, accessToken : String): BaseResponse + suspend fun refreshTokenData(refreshToken : String) : BaseResponse + + suspend fun spotLogout(): NullResultResponse } \ No newline at end of file diff --git a/data/login/src/main/java/com/umcspot/spot/login/datasourceimpl/LoginDataSourceImpl.kt b/data/login/src/main/java/com/umcspot/spot/login/datasourceimpl/LoginDataSourceImpl.kt index 29541bfe..b2c75c1a 100644 --- a/data/login/src/main/java/com/umcspot/spot/login/datasourceimpl/LoginDataSourceImpl.kt +++ b/data/login/src/main/java/com/umcspot/spot/login/datasourceimpl/LoginDataSourceImpl.kt @@ -1,19 +1,25 @@ package com.umcspot.spot.login.datasourceimpl -import androidx.datastore.core.DataStore import com.umcspot.spot.login.datasource.LoginDataSource import com.umcspot.spot.login.dto.response.TokenResponseDto import com.umcspot.spot.login.service.LoginService import com.umcspot.spot.network.model.BaseResponse +import com.umcspot.spot.network.model.NullResultResponse import javax.inject.Inject class LoginDataSourceImpl @Inject constructor( private val loginService: LoginService ) : LoginDataSource { - override suspend fun finishSocialLogin( + override suspend fun getCallBackToken( type: String, accessToken: String ): BaseResponse = loginService.getCallBackToken(type, accessToken) + + override suspend fun refreshTokenData(refreshToken: String): BaseResponse = + loginService.refreshTokenData(refreshToken) + + override suspend fun spotLogout() : NullResultResponse = + loginService.spotLogout() } \ No newline at end of file diff --git a/data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt b/data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt index 838a7e6f..3da49b17 100644 --- a/data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt +++ b/data/login/src/main/java/com/umcspot/spot/login/repositoryimpl/LoginRepositoryImpl.kt @@ -3,15 +3,16 @@ package com.umcspot.spot.login.repositoryimpl import androidx.datastore.core.DataStore import com.umcspot.spot.datastore.token.SpotTokenData import com.umcspot.spot.datastore.userId.SpotUserIdData +import com.umcspot.spot.login.datasource.LoginDataSource import com.umcspot.spot.login.mapper.toDomain -import com.umcspot.spot.login.service.LoginService import com.umcspot.spot.model.SocialLoginType import com.umcspot.spot.token.model.TokenResult import com.umcspot.spot.token.repository.TokenRepository +import kotlinx.coroutines.flow.first import javax.inject.Inject class LoginRepositoryImpl @Inject constructor( - private val studyService: LoginService, + private val loginDataStore: LoginDataSource, private val spotTokenDataStore: DataStore, private val spotUserIdDataStore: DataStore ) : TokenRepository { @@ -19,9 +20,9 @@ class LoginRepositoryImpl @Inject constructor( override suspend fun finishSocialLogin( type: SocialLoginType, accessToken: String - ): Result = + ): Result = runCatching { - val response = studyService.getCallBackToken(type.title, accessToken) + val response = loginDataStore.getCallBackToken(type.title, accessToken) val tokenResult: TokenResult = response.result.toDomain() spotTokenDataStore.updateData { current -> @@ -36,7 +37,45 @@ class LoginRepositoryImpl @Inject constructor( userId = tokenResult.userId ) } + } + + override suspend fun refreshTokenData( + ): Result = + runCatching { + val tokenData = spotTokenDataStore.data.first() + val refreshToken = tokenData.refreshToken + + + require(refreshToken.isNotBlank()) { "Refresh token is empty" } + + val response = loginDataStore.refreshTokenData(refreshToken) + val tokenResult: TokenResult = response.result.toDomain() + + spotTokenDataStore.updateData { current -> + current.copy( + accessToken = tokenResult.accessToken, + refreshToken = tokenResult.refreshToken + ) + } - tokenResult + spotUserIdDataStore.updateData { current -> + current.copy(userId = tokenResult.userId) + } + } + + override suspend fun spotLogout(): Result = + runCatching { + loginDataStore.spotLogout().code + + spotTokenDataStore.updateData { current -> + current.copy( + accessToken = "", + refreshToken = "" + ) + } + + spotUserIdDataStore.updateData { current -> + current.copy(userId = "") + } } -} \ No newline at end of file +} diff --git a/data/login/src/main/java/com/umcspot/spot/login/service/LoginService.kt b/data/login/src/main/java/com/umcspot/spot/login/service/LoginService.kt index 947e19da..caee04c3 100644 --- a/data/login/src/main/java/com/umcspot/spot/login/service/LoginService.kt +++ b/data/login/src/main/java/com/umcspot/spot/login/service/LoginService.kt @@ -2,7 +2,11 @@ package com.umcspot.spot.login.service import com.umcspot.spot.login.dto.response.TokenResponseDto import com.umcspot.spot.network.model.BaseResponse +import com.umcspot.spot.network.model.NullResultResponse import retrofit2.http.GET +import retrofit2.http.HEAD +import retrofit2.http.Header +import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query @@ -14,4 +18,13 @@ interface LoginService { @Query("accessToken") accessToken : String ) : BaseResponse + @POST("/api/auth/reissue") + suspend fun refreshTokenData( + @Header("refreshToken") refreshToken : String, + ) : BaseResponse + + @POST("/api/auth/logout") + suspend fun spotLogout( + ) : NullResultResponse + } diff --git a/domain/token/src/main/java/com/umcspot/spot/token/repository/TokenRepository.kt b/domain/token/src/main/java/com/umcspot/spot/token/repository/TokenRepository.kt index 7df52445..eb2ef397 100644 --- a/domain/token/src/main/java/com/umcspot/spot/token/repository/TokenRepository.kt +++ b/domain/token/src/main/java/com/umcspot/spot/token/repository/TokenRepository.kt @@ -1,8 +1,11 @@ package com.umcspot.spot.token.repository import com.umcspot.spot.model.SocialLoginType -import com.umcspot.spot.token.model.TokenResult interface TokenRepository { - suspend fun finishSocialLogin(type : SocialLoginType, accessToken : String) : Result + suspend fun finishSocialLogin(type : SocialLoginType, accessToken : String) : Result + + suspend fun refreshTokenData() : Result + + suspend fun spotLogout(): Result } \ No newline at end of file diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index e5d6569c..0000e9ed 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -9,6 +9,7 @@ android { dependencies { implementation(projects.domain.study) implementation(projects.domain.user) + implementation(projects.domain.token) implementation(projects.core.designsystem) implementation(projects.core.common) } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt index 4cebc772..002e7819 100644 --- a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,6 +37,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.launch import com.umcspot.spot.designsystem.R import com.umcspot.spot.designsystem.component.ProfileImage import com.umcspot.spot.designsystem.component.SpotSpinner @@ -70,6 +72,7 @@ fun MyPageScreen( viewmodel : MyPageViewModel = hiltViewModel() ) { val uiState by viewmodel.uiState.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() LaunchedEffect(Unit) { viewmodel.load() @@ -90,7 +93,12 @@ fun MyPageScreen( onEditInterestClick = onEditInterestClick, onEditInterestLocationClick = onEditInterestLocationClick, onCancelMemberShipClick = onCancelMemberShipClick, - onLogoutClick = onLogoutClick + onLogoutClick = { + scope.launch { + viewmodel.logout() + onLogoutClick() + } + } ) } diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt index 5f624c23..5cfd13c7 100644 --- a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt @@ -6,6 +6,7 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.umcspot.spot.ui.state.UiState +import com.umcspot.spot.token.repository.TokenRepository import com.umcspot.spot.user.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -17,6 +18,7 @@ import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( private val userRepository: UserRepository, + private val tokenRepository: TokenRepository, application: Application ) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(MyPageState()) @@ -100,11 +102,9 @@ class MyPageViewModel @Inject constructor( } } catch (e: Exception) { Log.e("MyPageViewModel", "loadAppVersion error", e) -// _uiState.update { -// it.copy(appVersion = UiState.Failure(e.message ?: "앱 버전 조회 실패")) -// } } } + suspend fun logout() = tokenRepository.spotLogout() } diff --git a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt index ec1ef48c..3b96c822 100644 --- a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt +++ b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingScreen.kt @@ -44,6 +44,7 @@ import kotlinx.coroutines.flow.collectLatest @Composable fun LandingRoute( navigateToSignUp: () -> Unit, + navigateToHome: () -> Unit, modifier: Modifier = Modifier, viewModel: LandingViewModel = hiltViewModel(), ) { @@ -54,10 +55,15 @@ fun LandingRoute( val snackBarHostState = remember { SnackbarHostState() } + LaunchedEffect(Unit) { + viewModel.tryAutoLogin() + } + LaunchedEffect(viewModel.sideEffect) { viewModel.sideEffect.collectLatest { effect -> when (effect) { - is LandingSideEffect.NavigateToHome -> navigateToSignUp() + is LandingSideEffect.NavigateToHome -> navigateToHome() + is LandingSideEffect.NavigateToSignUp -> navigateToSignUp() is LandingSideEffect.ShowSnackBar -> { snackBarHostState.showSnackbar(effect.message) } @@ -139,4 +145,4 @@ fun LandingScreen( NaverStartButton(onClick = onNaverClick) } } -} \ No newline at end of file +} diff --git a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingState.kt b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingState.kt index 073c5897..26d669ef 100644 --- a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingState.kt +++ b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingState.kt @@ -6,5 +6,6 @@ data class LandingState( sealed interface LandingSideEffect { data object NavigateToHome : LandingSideEffect + data object NavigateToSignUp : LandingSideEffect data class ShowSnackBar(val message: String) : LandingSideEffect } \ No newline at end of file diff --git a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt index 2d27871c..9a57c4d5 100644 --- a/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt +++ b/feature/signup/src/main/java/com/umcspot/spot/signup/landing/LandingViewModel.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.viewModelScope import com.kakao.sdk.user.UserApiClient import com.navercorp.nid.NidOAuth import com.navercorp.nid.oauth.util.NidOAuthCallback -import com.umcspot.spot.common.util.runSuspendCatching import com.umcspot.spot.model.SocialLoginType import com.umcspot.spot.token.repository.TokenRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -34,6 +33,26 @@ class LandingViewModel @Inject constructor( private val _sideEffect = MutableSharedFlow() val sideEffect = _sideEffect.asSharedFlow() + private var autoLoginChecked = false + + fun tryAutoLogin() { + if (autoLoginChecked) return + autoLoginChecked = true + + if (_uiState.value.isLoading) return + _uiState.update { it.copy(isLoading = true) } + + viewModelScope.launch { + val result = loginRepository.refreshTokenData() + if (result.isSuccess) { + _uiState.update { it.copy(isLoading = false) } + _sideEffect.emit(LandingSideEffect.NavigateToHome) + } else { + _uiState.update { it.copy(isLoading = false) } + } + } + } + fun startSocialLogin( type: SocialLoginType, activity: Activity, @@ -91,13 +110,12 @@ class LandingViewModel @Inject constructor( private fun requestServerLogin(type: SocialLoginType, accessToken: String) = viewModelScope.launch { - runSuspendCatching { - loginRepository.finishSocialLogin(type = type, accessToken = accessToken) - }.onSuccess { + val result = loginRepository.finishSocialLogin(type = type, accessToken = accessToken) + if (result.isSuccess) { _uiState.update { it.copy(isLoading = false) } - _sideEffect.emit(LandingSideEffect.NavigateToHome) - }.onFailure { e -> - handleLoginError("서버 로그인 실패", e) + _sideEffect.emit(LandingSideEffect.NavigateToSignUp) + } else { + handleLoginError("서버 로그인 실패", result.exceptionOrNull()) } } @@ -108,4 +126,4 @@ class LandingViewModel @Inject constructor( _uiState.update { it.copy(isLoading = false) } _sideEffect.emit(LandingSideEffect.ShowSnackBar(errorMessage)) } -} \ No newline at end of file +} diff --git a/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt b/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt index 8bdb398d..d28e2c14 100644 --- a/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt +++ b/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt @@ -37,7 +37,8 @@ fun NavGraphBuilder.signupGraph( ) { composable { LandingRoute( - navigateToSignUp = navigateToSignUp + navigateToSignUp = navigateToSignUp, + navigateToHome = navigateToHome ) } composable { @@ -70,4 +71,4 @@ data object SignUp : Route data object CheckList : Route @Serializable -data object Saving : Route \ No newline at end of file +data object Saving : Route