Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ fun YappNavHost(
)
homeNavGraph(
navigateNotice = { navigator.navigateNoticeScreen() },
navigateSetting = { navigator.navigateSettingScreen() }
navigateSetting = { navigator.navigateSettingScreen() },
navigateLogin = {navigator.navigateLoginScreen(
navOptions = clearBackStackNavOptions
)}
)
settingNavGraph(
navigateLogin = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.yapp.model.UserInfo
import kotlinx.coroutines.flow.Flow

interface UserRepository {
suspend fun getUserAccessToken() : String
suspend fun deleteAccount()
suspend fun getUserProfile(): Flow<UserInfo>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.yapp.core.data.data.repository
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import com.yapp.core.data.local.SecurityPreferences
import com.yapp.core.data.remote.api.AlarmApi
import com.yapp.core.data.remote.model.request.DeviceAlarmRequest
import com.yapp.core.data.remote.model.request.FcmTokenRequest
Expand All @@ -13,7 +14,8 @@ import javax.inject.Inject
internal class AlarmRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val alarmApi: AlarmApi,
) : AlarmRepository {
private val securityPreferences: SecurityPreferences,
) : AlarmRepository {

override suspend fun updateFcmToken(fcmToken: String) {
alarmApi.putFcmToken(FcmTokenRequest(fcmToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import com.yapp.core.data.local.SecurityPreferences
import com.yapp.core.data.remote.api.UserApi
import com.yapp.dataapi.UserRepository
import com.yapp.model.UserInfo
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

internal class UserRepositoryImpl @Inject constructor(
private val userApi: UserApi,
private val securityPreferences: SecurityPreferences,
): UserRepository {
override suspend fun getUserAccessToken(): String {
return runBlocking { securityPreferences.flowAccessToken().firstOrNull() ?: "" }
}

override suspend fun deleteAccount() {
userApi.deleteUser()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.yapp.core.data.remote.api

import com.yapp.core.data.remote.model.request.LoginRequest
import com.yapp.core.data.remote.model.request.ReissueTokenRequest
import com.yapp.core.data.remote.model.request.SignUpRequest
import com.yapp.core.data.remote.model.response.LoginResponse
import com.yapp.core.data.remote.model.response.SignUpResponse
Expand All @@ -18,4 +19,9 @@ internal interface AuthApi {
suspend fun login(
@Body request : LoginRequest
) : LoginResponse

@POST("v1/auth/reissue-token")
suspend fun reissueToken(
@Body request: ReissueTokenRequest
): LoginResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact
import com.yapp.core.data.remote.Tag
import com.yapp.core.data.remote.retrofit.NullOnEmptyConverterFactory
import com.yapp.core.data.remote.retrofit.ResultCallAdapterFactory
import com.yapp.core.data.remote.retrofit.TokenAuthenticator
import com.yapp.core.data.remote.retrofit.TokenInterceptor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONArray
import org.json.JSONObject
import retrofit2.OptionalConverterFactory
import retrofit2.Retrofit
import timber.log.Timber
import javax.inject.Singleton
import kotlinx.serialization.json.Json
import retrofit2.OptionalConverterFactory

@Module
@InstallIn(SingletonComponent::class)
Expand Down Expand Up @@ -57,10 +58,12 @@ internal object NetworkModule {
@AuthOkHttpClient
fun provideAuthOkHttpClient(
tokenInterceptor: TokenInterceptor,
tokenAuthenticator: TokenAuthenticator,
loggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(tokenInterceptor)
.addInterceptor(loggingInterceptor)
.authenticator(tokenAuthenticator)
.build()

@Singleton
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.yapp.core.data.remote.model.request

import kotlinx.serialization.Serializable

@Serializable
internal data class ReissueTokenRequest(
val accessToken : String,
val refreshToken : String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.yapp.core.data.remote.retrofit

import com.yapp.core.data.local.SecurityPreferences
import com.yapp.core.data.remote.api.AuthApi
import com.yapp.core.data.remote.model.request.ReissueTokenRequest
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import javax.inject.Inject

internal class TokenAuthenticator @Inject constructor(
private val securityPreferences: SecurityPreferences,
private val authApi: AuthApi,
) : Authenticator {

override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
val accessToken = securityPreferences.flowAccessToken().firstOrNull().orEmpty()
val refreshToken = securityPreferences.flowRefreshToken().firstOrNull().orEmpty()

val request = ReissueTokenRequest(
accessToken = accessToken,
refreshToken = refreshToken
)

val (newAccessToken, newRefreshToken) = try {
val response = authApi.reissueToken(request)
securityPreferences.setAccessToken(response.accessToken)
securityPreferences.setRefreshToken(response.refreshToken)

response.accessToken to response.refreshToken
} catch (e: Exception) {
"" to ""
}

if (newAccessToken.isBlank() || newRefreshToken.isBlank()) {
resetToken()
return@runBlocking null
}

response.request.newBuilder()
.header("Authorization", "Bearer $newAccessToken")
.build()
}
}

private fun resetToken() {
runBlocking {
securityPreferences.setAccessToken("")
securityPreferences.setRefreshToken("")
}
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿงน Nitpick (assertive)

resetToken์—์„œ runBlocking ์‚ฌ์šฉ ์‹œ์ ๋„ ๋น„๋™๊ธฐ ์ „ํ™˜์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

ํ† ํฐ ๋ฆฌ์…‹ ์‹œ์ ์—์„œ ์—ญ์‹œ runBlocking์ด ๋™์ž‘ ์Šค๋ ˆ๋“œ๋ฅผ ์ž ๊ธ€ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ „์ฒด ๊ตฌ์กฐ๊ฐ€ ํ—ˆ์šฉํ•œ๋‹ค๋ฉด ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•ด UI ์‘๋‹ต์„ฑ์„ ๋”์šฑ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ํ•ด ๋ณด์„ธ์š”.

Copy link
Member

Choose a reason for hiding this comment

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

A: resetToken ๋ถ€๋ถ„์€ suspend function์œผ๋กœ ์žก์œผ๋ฉด ์ข‹๊ธด ํ•˜๊ฒ ๊ตฐ์š”

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class GetUserProfileUseCase @Inject constructor(
) {
suspend operator fun invoke() = userRepository.getUserProfile()

suspend fun getUserAccessToken() = userRepository.getUserAccessToken()
}


Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ enum class YappServerError(val exception: YappException) {
USR_1102(SignUpPendingException()),
USR_1103(RecentSignUpRejectedException()),
USR_1104(LoginBlockedException()),
USR_1105(LoginException())
USR_1105(LoginException()),

//ํ† ํฐ ๋งŒ๋ฃŒ
TKN_0001(InvalidTokenException()),
TKN_0002(InvalidTokenException())
}

class InternalServerException : YappException()
Expand All @@ -27,6 +31,7 @@ class SignUpPendingException : YappException("ํšŒ์›๊ฐ€์ž… ์ฒ˜๋ฆฌ๊ฐ€ ์ง„ํ–‰ ์ค‘
class RecentSignUpRejectedException : YappException("์ตœ๊ทผ์˜ ํšŒ์›๊ฐ€์ž… ์‹ ์ฒญ์€ ๊ฑฐ์ ˆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
class LoginBlockedException : YappException("๋กœ๊ทธ์ธ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ํšŒ์› ์ƒํƒœ์ž…๋‹ˆ๋‹ค.")
class LoginException : YappException("๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ณ„์ • ์ •๋ณด๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•˜์„ธ์š”.")
class InvalidTokenException : YappException("๋น„์ •์ƒ ํ† ํฐ์ž…๋‹ˆ๋‹ค")

open class YappException(message: String = "") : Exception(message) {
private var _message: String = message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fun NoticeItem(
noticeInfo: NoticeInfo,
) {
val columModifier = modifier
.fillMaxWidth()
.padding(vertical = 9.dp)
.then(onClick?.let { Modifier.yappClickable(onClick = it) } ?: Modifier)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ sealed interface HomeIntent {
sealed interface HomeSideEffect {
data object NavigateToNotice : HomeSideEffect
data object NavigateToSetting : HomeSideEffect
data object NavigateToLogin : HomeSideEffect
data class ShowToast(val message: String) : HomeSideEffect
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.yapp.feature.home.component.ProfileSection
internal fun HomeRoute(
navigateToNotice: () -> Unit,
navigateToSetting: () -> Unit,
navigateToLogin: () -> Unit,
viewModel: HomeViewModel = hiltViewModel(),
) {
LaunchedEffect(Unit) {
Expand All @@ -44,6 +45,7 @@ internal fun HomeRoute(
is HomeSideEffect.ShowToast -> {
Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
}
HomeSideEffect.NavigateToLogin -> navigateToLogin()
}
}

Expand Down Expand Up @@ -98,6 +100,6 @@ fun HomeScreen(
@Composable
private fun HomeScreenPreview() {
YappTheme {
HomeRoute({}, {})
HomeRoute({}, {},{})
}
}
13 changes: 11 additions & 2 deletions feature/home/src/main/java/com/yapp/feature/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import com.yapp.core.ui.mvi.mviIntentStore
import com.yapp.dataapi.PostsRepository
import com.yapp.domain.GetUserProfileUseCase
import com.yapp.model.NoticeType
import com.yapp.model.exceptions.InvalidTokenException
import com.yapp.model.exceptions.UserNotFoundForEmailException
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -39,16 +42,22 @@ class HomeViewModel @Inject constructor(
when (intent) {
HomeIntent.ClickMoreButton -> postSideEffect(HomeSideEffect.NavigateToNotice)
HomeIntent.ClickSettingButton -> postSideEffect(HomeSideEffect.NavigateToSetting)
HomeIntent.EnterHomeScreen -> { loadHomeInfo( reduce)
}
HomeIntent.EnterHomeScreen -> { loadHomeInfo( reduce,postSideEffect) }
}
}

private fun loadHomeInfo(
reduce: (HomeState.() -> HomeState) -> Unit,
postSideEffect: (HomeSideEffect) -> Unit
) = viewModelScope.launch {
reduce { copy(isLoading = true, isUserInfoLoading = true, isNoticesLoading = true) }
getUserProfileUseCase()
.catch { error->
when(error) {
is UserNotFoundForEmailException, is InvalidTokenException -> postSideEffect(HomeSideEffect.NavigateToLogin)
else -> throw error
}
}
.collectLatest{ userInfo ->
reduce {
copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ fun NavController.navigateToSetting(navOptions: NavOptions? = null) {

fun NavGraphBuilder.homeNavGraph(
navigateNotice: () -> Unit,
navigateSetting: () -> Unit
navigateSetting: () -> Unit,
navigateLogin : () -> Unit
) {
composable<HomeRoute> {
HomeRoute(
navigateToNotice = navigateNotice,
navigateToSetting = navigateSetting,
navigateToLogin = navigateLogin
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sealed interface LoginIntent {
data object ClickNextButton : LoginIntent
data object ClickTerms : LoginIntent
data object ClickPersonalPolicy : LoginIntent
data object EnterLoginScreen : LoginIntent
}

sealed interface LoginSideEffect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
Expand Down Expand Up @@ -35,6 +36,11 @@ internal fun LoginRoute(
) {
val uiState by viewModel.store.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current

LaunchedEffect(Unit) {
viewModel.store.onIntent(LoginIntent.EnterLoginScreen)
}

viewModel.store.sideEffects.collectWithLifecycle { effect ->
when (effect) {
LoginSideEffect.NavigateToSignUp -> navigateToSignup()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.yapp.core.ui.mvi.MviIntentStore
import com.yapp.core.ui.mvi.mviIntentStore
import com.yapp.domain.GetUserProfileUseCase
import com.yapp.domain.LoginUseCase
import com.yapp.model.Regex
import com.yapp.model.exceptions.InvalidRequestArgument
Expand All @@ -14,7 +15,9 @@ import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginUseCase: LoginUseCase,
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {

val store: MviIntentStore<LoginState, LoginIntent, LoginSideEffect> =
mviIntentStore(
initialState = LoginState(),
Expand Down Expand Up @@ -72,9 +75,9 @@ class LoginViewModel @Inject constructor(
}
postSideEffect(LoginSideEffect.NavigateToSignUp)
}

LoginIntent.ClickTerms -> postSideEffect(LoginSideEffect.ShowTerms)
LoginIntent.ClickPersonalPolicy -> postSideEffect(LoginSideEffect.ShowPersonalPolicy)
LoginIntent.EnterLoginScreen -> checkAccessToken(postSideEffect)
}
}

Expand Down Expand Up @@ -119,5 +122,12 @@ class LoginViewModel @Inject constructor(
}
}
}

private fun checkAccessToken(postSideEffect: (LoginSideEffect) -> Unit) = viewModelScope.launch {
val accessToken = getUserProfileUseCase.getUserAccessToken()
if (accessToken.isNotBlank()){
postSideEffect(LoginSideEffect.NavigateToHome)
}
}
}

Loading