diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12f04c0d..6cfab516 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,7 +115,7 @@ + android:scheme="kakao698fe2d5716b72acfef2760ff5ccd46e" /> diff --git a/app/src/main/java/com/sopt/geonppang/data/datasource/local/GPDataSource.kt b/app/src/main/java/com/sopt/geonppang/data/datasource/local/GPDataSource.kt index 6921b5d1..506182c4 100644 --- a/app/src/main/java/com/sopt/geonppang/data/datasource/local/GPDataSource.kt +++ b/app/src/main/java/com/sopt/geonppang/data/datasource/local/GPDataSource.kt @@ -52,6 +52,13 @@ class GPDataSource @Inject constructor(@ApplicationContext context: Context) { set(value) = dataStore.edit { putBoolean(IS_LOGIN, value) } get() = dataStore.getBoolean(IS_LOGIN, false) + var userRoleType: String + set(value) = dataStore.edit { putString(USER_ROLE_TYPE, value) } + get() = dataStore.getString( + USER_ROLE_TYPE, + "" + ) ?: "" + fun clear() { dataStore.edit { clear() @@ -64,5 +71,6 @@ class GPDataSource @Inject constructor(@ApplicationContext context: Context) { const val ACCESS_TOKEN = "AccessToken" const val REFRESH_TOKEN = "RefreshToken" const val IS_LOGIN = "IsLogin" + const val USER_ROLE_TYPE = "UserRoleType" } } diff --git a/app/src/main/java/com/sopt/geonppang/data/interceptor/AuthInterceptor.kt b/app/src/main/java/com/sopt/geonppang/data/interceptor/AuthInterceptor.kt index c858115e..e92ff4e9 100644 --- a/app/src/main/java/com/sopt/geonppang/data/interceptor/AuthInterceptor.kt +++ b/app/src/main/java/com/sopt/geonppang/data/interceptor/AuthInterceptor.kt @@ -17,21 +17,27 @@ class AuthInterceptor @Inject constructor( private val context: Application, ) : Interceptor { + // TODO dana 경우에 따른 분기 처리 필요 override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val authRequest = originalRequest.newBuilder().addHeader("Authorization", gpDataSource.accessToken) .build() - val response = chain.proceed(authRequest) + val response = chain.proceed( + if (gpDataSource.accessToken.isNotBlank()) { + authRequest + } else { + originalRequest + } + ) when (response.code) { 401 -> { response.close() - val refreshTokenRequest = originalRequest.newBuilder().get() - .url("${BuildConfig.GP_BASE_URL}auth/refresh") - .addHeader(ACCESS_TOKEN, gpDataSource.accessToken) - .addHeader(REFRESH_TOKEN, gpDataSource.refreshToken) - .build() + val refreshTokenRequest = + originalRequest.newBuilder().get().url("${BuildConfig.GP_BASE_URL}auth/refresh") + .addHeader(ACCESS_TOKEN, gpDataSource.accessToken) + .addHeader(REFRESH_TOKEN, gpDataSource.refreshToken).build() val refreshTokenResponse = chain.proceed(refreshTokenRequest) if (refreshTokenResponse.isSuccessful) { @@ -46,15 +52,16 @@ class AuthInterceptor @Inject constructor( refreshTokenResponse.close() val newRequest = originalRequest.newBuilder() - .addHeader(ACCESS_TOKEN, gpDataSource.accessToken) - .build() + .addHeader(ACCESS_TOKEN, gpDataSource.accessToken).build() return chain.proceed(newRequest) } else { with(context) { CoroutineScope(Dispatchers.Main).launch { startActivity( - Intent(this@with, SignActivity::class.java) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + Intent( + this@with, + SignActivity::class.java + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) } } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/auth/AuthViewModel.kt b/app/src/main/java/com/sopt/geonppang/presentation/auth/AuthViewModel.kt index d7dc02a9..da8129c5 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/auth/AuthViewModel.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/auth/AuthViewModel.kt @@ -11,6 +11,7 @@ import com.sopt.geonppang.domain.repository.AuthRepository import com.sopt.geonppang.domain.repository.ValidationRepository import com.sopt.geonppang.presentation.type.AuthRoleType import com.sopt.geonppang.presentation.type.PlatformType +import com.sopt.geonppang.presentation.type.UserRoleType import com.sopt.geonppang.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -85,6 +86,9 @@ class AuthViewModel @Inject constructor( isValidNickname && isNicknameUsable }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) + val platformType = gpDataSource.platformType + + // TODO: 초기화 부분 고민 fun initNickname() { _isNicknameUsable.value = UiState.Empty } @@ -112,14 +116,11 @@ class AuthViewModel @Inject constructor( .onSuccess { if (it.code == 200) { _isNicknameUsable.value = UiState.Success(true) - Timber.tag("isNicknameDuplicated") - .e("{" + it.message + " " + _isNicknameUsable.value + "}") } } .onFailure { throwable -> Timber.e(throwable.message) _isNicknameUsable.value = UiState.Error("false") - Timber.tag("isNicknameNotDuplicate").e("{" + _isNicknameUsable.value + "}") } } } @@ -128,6 +129,8 @@ class AuthViewModel @Inject constructor( gpDataSource.isLogin = true } + // 카카오 로그인 이거나, 자체 회원 가입인 경우는 -> role이 USER + // 카카오 회원 가입인 경우만 -> role 이 GUEST -> 이 경우메만 닉네임 설정 뷰로 이동 fun signUp( platformType: PlatformType, platformToken: String, @@ -151,13 +154,24 @@ class AuthViewModel @Inject constructor( val responseHeader = signUpResponse.headers() val accessToken = responseHeader[AUTHORIZATION].toString() val refreshToken = responseHeader[AUTHORIZATION_REFRESH].toString() + _authRoleType.value = - if (responseBody?.role == AuthRoleType.GUEST.name) AuthRoleType.GUEST else AuthRoleType.USER + if (responseBody?.role == AuthRoleType.ROLE_GUEST.name) { + AuthRoleType.ROLE_GUEST + } else { + AuthRoleType.ROLE_MEMBER + } + gpDataSource.accessToken = BEARER_PREFIX + accessToken - if (_authRoleType.value == AuthRoleType.USER) { + + // 카카오 로그인, 자체 회원 가입인 경우메만 리프래시 토큰을 저장하고 회원가입 상태를 success로 지정 + if (_authRoleType.value == AuthRoleType.ROLE_MEMBER) { gpDataSource.refreshToken = BEARER_PREFIX + refreshToken + + _signUpState.value = UiState.Success(true) } - _signUpState.value = UiState.Success(true) + + // amplitude를 쏘기 위한 것 if (platformType == PlatformType.NONE) { _memberId.value = responseBody?.memberId } @@ -168,22 +182,25 @@ class AuthViewModel @Inject constructor( } } + // 소셜 회원가입 후에만 사용하는 api fun settingNickName() { viewModelScope.launch { - nickname.value?.let { nickname -> + nickname.value.let { nickname -> authRepository.settingNickname(RequestNicknameSetting(nickname)) .onSuccess { response -> val responseHeader = response.headers() val responseBody = response.body() + + // 소셜 회원가입 시 여기서 다시 엑세스, 리프래시 토큰을 발급 받음 val accessToken = responseHeader[AUTHORIZATION].toString() val refreshToken = responseHeader[AUTHORIZATION_REFRESH].toString() + gpDataSource.accessToken = BEARER_PREFIX + accessToken gpDataSource.refreshToken = BEARER_PREFIX + refreshToken _signUpState.value = UiState.Success(true) - // 소셜 회원가입 시 자동 로그인 설정 - setAutoLogin() _memberId.value = responseBody?.data?.memberId + gpDataSource.userRoleType = UserRoleType.FILTER_UNSELECTED_MEMBER.name } .onFailure { throwable -> Timber.e(throwable.message) diff --git a/app/src/main/java/com/sopt/geonppang/presentation/auth/SignActivity.kt b/app/src/main/java/com/sopt/geonppang/presentation/auth/SignActivity.kt index d182f891..2923a950 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/auth/SignActivity.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/auth/SignActivity.kt @@ -13,7 +13,9 @@ import com.sopt.geonppang.presentation.MainActivity import com.sopt.geonppang.presentation.login.LoginActivity import com.sopt.geonppang.presentation.type.AuthRoleType import com.sopt.geonppang.presentation.type.PlatformType +import com.sopt.geonppang.presentation.type.UserRoleType import com.sopt.geonppang.util.AmplitudeUtils +import com.sopt.geonppang.util.UiState import com.sopt.geonppang.util.binding.BindingActivity import com.sopt.geonppang.util.extension.setOnSingleClickListener import dagger.hilt.android.AndroidEntryPoint @@ -45,17 +47,25 @@ class SignActivity : moveToSignUp() AmplitudeUtils.trackEventWithProperties(START_SIGNUP, SIGNUP_TYPE, EMAIL) } + + // TODO: dana 둘러보기 코드 작성 후 지우기 + binding.ivLogoText.setOnSingleClickListener { + GPDataSource(this).userRoleType = UserRoleType.NONE_MEMBER.name + moveToMain() + } } private fun collectData() { + // 카카오 회원 가입, 로그인 authViewModel.authRoleType.flowWithLifecycle(lifecycle).onEach { role -> when (role) { - AuthRoleType.GUEST -> { + // 카카오 회원가입인 경우 닉네임 페이지로 이동 + AuthRoleType.ROLE_GUEST -> { moveToNickNameSetting() } - AuthRoleType.USER -> { - authViewModel.setAutoLogin() + // 카카오 로그인인 경우 홈으로 이동 + AuthRoleType.ROLE_MEMBER -> { AmplitudeUtils.trackEvent(LOGIN_APP) moveToMain() } @@ -63,6 +73,16 @@ class SignActivity : else -> {} } }.launchIn(lifecycleScope) + + authViewModel.signUpState.flowWithLifecycle(lifecycle).onEach { signUpState -> + when (signUpState) { + is UiState.Success -> { + authViewModel.setAutoLogin() + } + + else -> {} + } + }.launchIn(lifecycleScope) } private fun moveToSignUp() { diff --git a/app/src/main/java/com/sopt/geonppang/presentation/auth/SignUpNicknameActivity.kt b/app/src/main/java/com/sopt/geonppang/presentation/auth/SignUpNicknameActivity.kt index 39f24817..bc03c02e 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/auth/SignUpNicknameActivity.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/auth/SignUpNicknameActivity.kt @@ -6,7 +6,6 @@ import androidx.activity.viewModels import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.sopt.geonppang.R -import com.sopt.geonppang.data.datasource.local.GPDataSource import com.sopt.geonppang.databinding.ActivitySignupNicknameBinding import com.sopt.geonppang.presentation.type.PlatformType import com.sopt.geonppang.util.AmplitudeUtils @@ -50,6 +49,7 @@ class SignUpNicknameActivity : viewModel.signUpState.flowWithLifecycle(lifecycle).onEach { when (it) { is UiState.Success -> { + // 회원가입 성공시에만 자동 로그인 설정 (소셜 회원가입도 닉네임까지 설정이 완료된 시점에) viewModel.setAutoLogin() AmplitudeUtils.trackEventWithProperties( COMPLETE_NICKNAME, @@ -83,6 +83,7 @@ class SignUpNicknameActivity : viewModel.initNickname() } }.launchIn(lifecycleScope) + viewModel.memberId.flowWithLifecycle(lifecycle).onEach { memberId -> if (memberId != null) AmplitudeUtils.setUserId(GUNBBANG + memberId) @@ -90,12 +91,13 @@ class SignUpNicknameActivity : } private fun completeSignUp() { - val gpDataSource = GPDataSource(this) - when (gpDataSource.platformType) { + when (viewModel.platformType) { + // 카카오 회원가입 PlatformType.KAKAO.name -> { viewModel.settingNickName() } + // 자체 회원가입 PlatformType.NONE.name -> { val email = intent.getStringExtra(EMAIL) val password = intent.getStringExtra(PASSWORD) diff --git a/app/src/main/java/com/sopt/geonppang/presentation/filterSetting/FilterSettingViewModel.kt b/app/src/main/java/com/sopt/geonppang/presentation/filterSetting/FilterSettingViewModel.kt index 16c772bf..24f48561 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/filterSetting/FilterSettingViewModel.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/filterSetting/FilterSettingViewModel.kt @@ -10,6 +10,7 @@ import com.sopt.geonppang.presentation.type.BreadFilterType import com.sopt.geonppang.presentation.type.FilterInfoType import com.sopt.geonppang.presentation.type.MainPurposeFilterType import com.sopt.geonppang.presentation.type.NutrientFilterType +import com.sopt.geonppang.presentation.type.UserRoleType import com.sopt.geonppang.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -23,7 +24,7 @@ import javax.inject.Inject @HiltViewModel class FilterSettingViewModel @Inject constructor( - gpDataSource: GPDataSource, + private val gpDataSource: GPDataSource, private val filterRepository: FilterSettingRepository ) : ViewModel() { private val _selectedFilterState = @@ -100,6 +101,10 @@ class FilterSettingViewModel @Inject constructor( ingredientType = _nutrientFilterType.value, ) ) + + // 필터를 설정할 때, userRoleType을 selectedMember로 설정 하기 + // TODO: dana filter 설정할 때마다 재설정하는게 맞는가,, + gpDataSource.userRoleType = UserRoleType.FILTER_SELECTED_MEMBER.name } .onFailure { throwable -> _selectedFilterState.value = UiState.Error(throwable.message) diff --git a/app/src/main/java/com/sopt/geonppang/presentation/home/HomeFragment.kt b/app/src/main/java/com/sopt/geonppang/presentation/home/HomeFragment.kt index 37651cc3..de4a33a5 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/home/HomeFragment.kt @@ -14,6 +14,7 @@ import com.sopt.geonppang.presentation.detail.DetailActivity.Companion.VIEW_DETA import com.sopt.geonppang.presentation.filterSetting.FilterSettingActivity import com.sopt.geonppang.presentation.search.SearchActivity import com.sopt.geonppang.presentation.type.FilterInfoType +import com.sopt.geonppang.presentation.type.UserRoleType import com.sopt.geonppang.util.AmplitudeUtils import com.sopt.geonppang.util.UiState import com.sopt.geonppang.util.binding.BindingFragment @@ -84,13 +85,24 @@ class HomeFragment : BindingFragment(R.layout.fragment_home } }.launchIn(lifecycleScope) - viewModel.isFilterSelected.flowWithLifecycle(lifecycle).onEach { isFilterSelected -> - binding.tvHomeBestBakeryTitle1.setVisibility(isFilterSelected) - binding.tvHomeBestReviewTitle1.setVisibility(isFilterSelected) - binding.includeHomeSpeechBubble.root.setVisibility(!isFilterSelected) + // 유저 상태에 따른 ui 분기 처리 + viewModel.userRoleType.flowWithLifecycle(lifecycle).onEach { + when (it) { + UserRoleType.NONE_MEMBER.name -> { + viewModel.setNickName("별사탕") + } + + UserRoleType.FILTER_SELECTED_MEMBER.name -> { + binding.tvHomeBestBakeryTitle1.setVisibility(true) + binding.tvHomeBestReviewTitle1.setVisibility(true) + } - if (isFilterSelected != null) - viewModel.fetchBestBakeryList() + UserRoleType.FILTER_UNSELECTED_MEMBER.name -> { + binding.includeHomeSpeechBubble.root.setVisibility(true) + } + + else -> {} + } }.launchIn(lifecycleScope) } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/home/HomeViewModel.kt b/app/src/main/java/com/sopt/geonppang/presentation/home/HomeViewModel.kt index d55bbb04..842b8178 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/home/HomeViewModel.kt @@ -2,10 +2,12 @@ package com.sopt.geonppang.presentation.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.sopt.geonppang.data.datasource.local.GPDataSource import com.sopt.geonppang.domain.model.BestBakery import com.sopt.geonppang.domain.model.BestReview import com.sopt.geonppang.domain.repository.GetUserFilterRepository import com.sopt.geonppang.domain.repository.HomeRepository +import com.sopt.geonppang.presentation.type.UserRoleType import com.sopt.geonppang.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -16,6 +18,7 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( + private val gpDataSource: GPDataSource, private val homeRepository: HomeRepository, private val getUserFilterRepository: GetUserFilterRepository ) : ViewModel() { @@ -24,30 +27,33 @@ class HomeViewModel @Inject constructor( private var _bestReviewListState = MutableStateFlow>>(UiState.Loading) val bestReviewListState get() = _bestReviewListState.asStateFlow() + private val _nickName = MutableStateFlow("") val nickName get() = _nickName.asStateFlow() - private val _isFilterSelected = MutableStateFlow(true) - val isFilterSelected get() = _isFilterSelected.asStateFlow() + private val _userRoleType = MutableStateFlow(gpDataSource.userRoleType) + val userRoleType get() = _userRoleType.asStateFlow() + + // TODO: 무슨 회원인지 로컬 에서 관리 init { - getUserFilter() + // 비회원이 아닌 경우에만 호출 되도록 + if (_userRoleType.value != UserRoleType.NONE_MEMBER.name) { + getUserFilter() + } + fetchBestList() } - fun fetchBestBakeryList() { + // 하나의 스코프를 만들어서 각 함수들이 동기적으로 처리되도록 수정 + fun fetchBestList() { viewModelScope.launch { homeRepository.fetchBestBakery() .onSuccess { bestBakeryList -> _bestBakeryListState.value = UiState.Success(bestBakeryList) - fetchBestReviewList() } .onFailure { throwable -> _bestBakeryListState.value = UiState.Error(throwable.message) } - } - } - private fun fetchBestReviewList() { - viewModelScope.launch { homeRepository.fetchBestReview() .onSuccess { bestReviewList -> _bestReviewListState.value = UiState.Success(bestReviewList) @@ -58,12 +64,23 @@ class HomeViewModel @Inject constructor( } } + // 유저가 필터를 선택 했는지 안 했는지를 구분 하기 위한 api 호출 private fun getUserFilter() { viewModelScope.launch { getUserFilterRepository.getUserFilter() .onSuccess { userFilterInfo -> - _nickName.value = userFilterInfo.nickName - _isFilterSelected.value = (userFilterInfo.mainPurpose != NONE) + setNickName(userFilterInfo.nickName) + + // 홈에서 필터 조회를 해서 유저 상태 설정 + val userRoleType = + if (userFilterInfo.mainPurpose == NONE) { + UserRoleType.FILTER_UNSELECTED_MEMBER.name + } else { + UserRoleType.FILTER_SELECTED_MEMBER.name + } + + gpDataSource.userRoleType = userRoleType + _userRoleType.value = userRoleType } .onFailure { throwable -> Timber.e(throwable.message) @@ -71,6 +88,10 @@ class HomeViewModel @Inject constructor( } } + fun setNickName(nickName: String) { + _nickName.value = nickName + } + companion object { const val NONE = "NONE" } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/type/AuthRoleType.kt b/app/src/main/java/com/sopt/geonppang/presentation/type/AuthRoleType.kt index be2c3efb..1922a491 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/type/AuthRoleType.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/type/AuthRoleType.kt @@ -1,5 +1,8 @@ package com.sopt.geonppang.presentation.type +// authors: dana +// description: 회원가입 시 닉네임 설정까지 완료한 유저인지를 구분하기 위한 role Type + enum class AuthRoleType { - USER, GUEST + ROLE_MEMBER, ROLE_GUEST } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/type/UserRoleType.kt b/app/src/main/java/com/sopt/geonppang/presentation/type/UserRoleType.kt new file mode 100644 index 00000000..9dbce461 --- /dev/null +++ b/app/src/main/java/com/sopt/geonppang/presentation/type/UserRoleType.kt @@ -0,0 +1,8 @@ +package com.sopt.geonppang.presentation.type + +// authors: dana +// description: 앱 내에서 필터 선택 / 필터 선택 안한 / 비회원 상태를 구분 하기 위한 role Type + +enum class UserRoleType { + FILTER_SELECTED_MEMBER, FILTER_UNSELECTED_MEMBER, NONE_MEMBER +} diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 0a707b3c..237eee70 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -105,6 +105,7 @@ android:layout_height="wrap_content" android:text="@{@string/home_best_title1(viewModel.nickName)}" android:textAppearance="@style/TextAppearance.Title2" + android:visibility="gone" tools:text="바이블님 맞춤" />