Skip to content

Commit c57b62c

Browse files
authored
[Feat] Login 영역에서의 ProgressBar 추가 (#285)
* chore: Debug 환경에 대한 로깅 제거 * modify: uiState와 uiEvent를 기반으로 ProgressBar를 추가함 * chore: id ib로 변경 * chore: 소소한 매니패스트 오류 수정 * chore: 로그인 xml 조정 * chore: progress bar 색상 진하게 indeterminateTint * chore: 코틀린 버전 2.1.0 * modify: 공통의 UiEvent, UiState * chore: 코틀린 버전 1.9.0 * chore: 코틀린 버전 1.9.0에 맞춰서 compose 컴파일러 1.5.0 * feat: uiState에 따라 뷰를 보여주도록 적극적으로 수정 * feat: 토큰 valid api가 만들어짐에 따라 호출 api를 교체
1 parent 09d4d0f commit c57b62c

File tree

14 files changed

+178
-132
lines changed

14 files changed

+178
-132
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ android {
8080
}
8181

8282
composeOptions {
83-
kotlinCompilerExtensionVersion = "1.4.4"
83+
kotlinCompilerExtensionVersion = "1.5.0"
8484
}
8585

8686
splits {

app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,6 @@
117117
android:scheme="kakao${KAKAO_NATIVE_APP_KEY}" />
118118
</intent-filter>
119119
</activity>
120-
<activity
121-
android:name=".presentation.base.BaseActivity"
122-
android:exported="false">
123-
<meta-data
124-
android:name="android.app.lib_name"
125-
android:value="" />
126-
</activity>
127120
<activity
128121
android:name=".presentation.mypage.usernamechange.UserNameChangeActivity"
129122
android:exported="true"
@@ -161,22 +154,6 @@
161154
android:name="android.app.lib_name"
162155
android:value="" />
163156
</activity>
164-
<activity
165-
android:name=".presentation.review.report.OthersReviewDialogActivity"
166-
android:exported="true"
167-
android:theme="@style/Theme.MyDialog">
168-
<meta-data
169-
android:name="android.app.lib_name"
170-
android:value="" />
171-
</activity>
172-
<activity
173-
android:name=".presentation.common.MyReviewBottomSheetFragment"
174-
android:exported="true"
175-
android:theme="@style/Theme.MyDialog">
176-
<meta-data
177-
android:name="android.app.lib_name"
178-
android:value="" />
179-
</activity>
180157
<activity
181158
android:name=".presentation.review.report.ReportActivity"
182159
android:exported="true">
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.eatssu.android.data.dto.request
2+
3+
data class CheckValidTokenRequest(
4+
val token: String,
5+
)

app/src/main/java/com/eatssu/android/data/repository/OauthRepositoryImpl.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.eatssu.android.data.repository
22

3+
import com.eatssu.android.data.dto.request.CheckValidTokenRequest
34
import com.eatssu.android.data.dto.request.LoginWithKakaoRequest
45
import com.eatssu.android.data.dto.response.BaseResponse
56
import com.eatssu.android.data.dto.response.TokenResponse
@@ -21,4 +22,9 @@ class OauthRepositoryImpl @Inject constructor(private val oauthService: OauthSer
2122
flow {
2223
emit(oauthService.loginWithKakao(body))
2324
}
25+
26+
override suspend fun checkValidToken(body: CheckValidTokenRequest): Flow<BaseResponse<Boolean>> =
27+
flow {
28+
emit(oauthService.checkValidToken(body))
29+
}
2430
}

app/src/main/java/com/eatssu/android/data/service/OauthService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.eatssu.android.data.service
22

3+
import com.eatssu.android.data.dto.request.CheckValidTokenRequest
34
import com.eatssu.android.data.dto.request.LoginWithKakaoRequest
45
import com.eatssu.android.data.dto.response.BaseResponse
56
import com.eatssu.android.data.dto.response.TokenResponse
@@ -18,4 +19,9 @@ interface OauthService { //여기는 토큰이 없는 레트로핏을 끼웁니
1819
suspend fun loginWithKakao(
1920
@Body request: LoginWithKakaoRequest,
2021
): BaseResponse<TokenResponse>
22+
23+
@POST("oauths/valid/token")
24+
suspend fun checkValidToken(
25+
@Body request: CheckValidTokenRequest,
26+
): BaseResponse<Boolean>
2127
}

app/src/main/java/com/eatssu/android/di/NetworkModule.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ object NetworkModule {
4646
val loggingInterceptor = HttpLoggingInterceptor()
4747
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
4848

49-
OkHttpClient.Builder().addInterceptor(loggingInterceptor).addInterceptor(tokenInterceptor)
49+
OkHttpClient.Builder()
50+
.addInterceptor(loggingInterceptor)
51+
.addInterceptor(tokenInterceptor)
5052
.build()
5153
} else {
52-
val loggingInterceptor = HttpLoggingInterceptor()
53-
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
54-
55-
OkHttpClient.Builder().addInterceptor(loggingInterceptor).addInterceptor(tokenInterceptor)
54+
// 프로덕션 환경에서는 로깅 인터셉터를 추가하지 않음
55+
OkHttpClient.Builder()
56+
.addInterceptor(tokenInterceptor)
5657
.build()
5758
}
5859

app/src/main/java/com/eatssu/android/domain/repository/OauthRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.eatssu.android.domain.repository
22

3+
import com.eatssu.android.data.dto.request.CheckValidTokenRequest
34
import com.eatssu.android.data.dto.request.LoginWithKakaoRequest
45
import com.eatssu.android.data.dto.response.BaseResponse
56
import com.eatssu.android.data.dto.response.TokenResponse
@@ -12,5 +13,6 @@ interface OauthRepository {
1213

1314
suspend fun login(body: LoginWithKakaoRequest): Flow<BaseResponse<TokenResponse>>
1415

16+
suspend fun checkValidToken(body: CheckValidTokenRequest): Flow<BaseResponse<Boolean>>
1517
}
1618

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.eatssu.android.domain.usecase.auth
22

3+
import com.eatssu.android.data.dto.request.CheckValidTokenRequest
34
import com.eatssu.android.data.dto.response.BaseResponse
4-
import com.eatssu.android.domain.repository.UserRepository
5+
import com.eatssu.android.domain.repository.OauthRepository
56
import kotlinx.coroutines.flow.Flow
67
import javax.inject.Inject
78

89
class GetIsAccessTokenValidUseCase @Inject constructor(
9-
private val userRepository: UserRepository,
10+
private val oauthRepository: OauthRepository
1011
) {
11-
suspend operator fun invoke(): Flow<BaseResponse<Boolean>> =
12-
userRepository.checkUserNameValidation("qkqh") //todo api 만들어지면 수정
12+
suspend operator fun invoke(userAccessToken: String): Flow<BaseResponse<Boolean>> =
13+
oauthRepository.checkValidToken(CheckValidTokenRequest(userAccessToken))
1314
}

app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,29 @@ class IntroViewModel @Inject constructor(
3333

3434
private fun autoLogin() {
3535
viewModelScope.launch {
36-
_uiState.value = UiState.Loading
36+
val userAccessToken = getAccessTokenUseCase()
3737

38+
_uiState.value = UiState.Loading
3839
try {
3940
// 토큰 존재 여부 확인
40-
if (getAccessTokenUseCase().isEmpty()) {
41+
if (userAccessToken.isEmpty()) {
4142
_uiState.value = UiState.Error
4243
_uiEvent.emit(UiEvent.ShowToast("로그인이 필요합니다"))
4344
return@launch
45+
} else {
46+
checkValid(userAccessToken)
4447
}
4548

46-
checkValid()
47-
4849
} catch (e: Exception) {
4950
_uiState.value = UiState.Error
5051
_uiEvent.emit(UiEvent.ShowToast("오류가 발생했습니다: ${e.message}"))
5152
}
5253
}
5354
}
5455

55-
private fun checkValid() {
56+
private fun checkValid(userAccessToken: String) {
5657
viewModelScope.launch {
57-
getIsAccessTokenValidUseCase()
58+
getIsAccessTokenValidUseCase(userAccessToken)
5859
.collect {
5960
if (it.result == true) { //토큰이 있고 유효함
6061
_uiState.value = UiState.Success(IntroState.ValidToken)

app/src/main/java/com/eatssu/android/presentation/login/LoginActivity.kt

Lines changed: 78 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package com.eatssu.android.presentation.login
33
import android.os.Bundle
44
import android.view.View
55
import androidx.activity.viewModels
6+
import androidx.lifecycle.Lifecycle
67
import androidx.lifecycle.lifecycleScope
8+
import androidx.lifecycle.repeatOnLifecycle
9+
import com.eatssu.android.R
710
import com.eatssu.android.databinding.ActivityLoginBinding
11+
import com.eatssu.android.presentation.UiEvent
12+
import com.eatssu.android.presentation.UiState
813
import com.eatssu.android.presentation.base.BaseActivity
914
import com.eatssu.android.presentation.main.MainActivity
1015
import com.eatssu.android.presentation.util.showToast
@@ -13,7 +18,6 @@ import com.kakao.sdk.common.model.ClientError
1318
import com.kakao.sdk.common.model.ClientErrorCause
1419
import com.kakao.sdk.user.UserApiClient
1520
import dagger.hilt.android.AndroidEntryPoint
16-
import kotlinx.coroutines.flow.collectLatest
1721
import kotlinx.coroutines.launch
1822
import timber.log.Timber
1923

@@ -26,73 +30,100 @@ class LoginActivity :
2630

2731
override fun onCreate(savedInstanceState: Bundle?) {
2832
super.onCreate(savedInstanceState)
33+
initUi()
34+
observeState()
35+
observeEvents()
36+
}
2937

30-
// 툴바 사용하지 않도록 설정
31-
toolbar.let {
32-
toolbar.visibility = View.GONE
33-
toolbarTitle.visibility = View.GONE
34-
setSupportActionBar(it)
35-
supportActionBar?.setDisplayHomeAsUpEnabled(false)
36-
supportActionBar?.setDisplayShowTitleEnabled(false)
38+
private fun initUi() {
39+
// 툴바 숨기기
40+
with(toolbar) {
41+
visibility = View.GONE
42+
setSupportActionBar(this)
43+
supportActionBar?.apply {
44+
setDisplayHomeAsUpEnabled(false)
45+
setDisplayShowTitleEnabled(false)
46+
}
3747
}
3848

39-
setOnClickListener()
49+
binding.ibKakaoLogin.setOnClickListener {
50+
handleKakaoLogin()
51+
}
4052
}
4153

42-
43-
fun setOnClickListener() {
44-
val context = this
45-
binding.mcvKakaoLogin.setOnClickListener {
46-
47-
Timber.d("버튼 클릭")
48-
lifecycleScope.launch {
49-
try {
50-
// 서비스 코드에서는 간단하게 로그인 요청하고 oAuthToken 을 받아올 수 있다.
51-
val oAuthToken = UserApiClient.loginWithKakao(context)
52-
Timber.d("beanbean > $oAuthToken")
53-
postUserInfo()
54-
55-
} catch (error: Throwable) {
56-
if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
57-
Timber.d("사용자가 명시적으로 취소")
58-
} else {
59-
Timber.e(error, "인증 에러 발생")
60-
}
54+
//kakao login sdk를 통해 유저 정보를 가져와 rest api 호출하는 뷰모델 함수 호출
55+
private fun handleKakaoLogin() {
56+
lifecycleScope.launch {
57+
try {
58+
loginViewModel.setLoadingState()
59+
val oAuthToken = UserApiClient.loginWithKakao(this@LoginActivity)
60+
Timber.d("Kakao login success: $oAuthToken")
61+
UserApiClient.instance.me { user, error ->
62+
user?.let {
63+
val providerID = user.id.toString()
64+
val email = user.kakaoAccount?.email.toString()
65+
loginViewModel.getKakaoLogin(email, providerID)
66+
} ?: Timber.e(error, "User info fetch failed")
6167
}
68+
} catch (error: Throwable) {
69+
handleKakaoLoginError(error)
6270
}
6371
}
6472
}
6573

74+
//kakao login sdk의 error를 다룹니다.
75+
private fun handleKakaoLoginError(error: Throwable) {
76+
when {
77+
error is ClientError && error.reason == ClientErrorCause.Cancelled -> {
78+
Timber.d("User cancelled login")
79+
loginViewModel.setInitState()
80+
}
6681

67-
private fun postUserInfo() {
68-
UserApiClient.instance.me { user, error ->
69-
if (user != null) {
70-
// 유저의 아이디
71-
Timber.d("invoke: id =" + user.id)
72-
val providerID = user.id.toString()
73-
// 유저의 이메일
74-
Timber.d("invoke: email =" + user.kakaoAccount!!.email)
75-
val email = user.kakaoAccount!!.email.toString()
76-
77-
loginViewModel.getLogin(email, providerID)
82+
else -> {
83+
Timber.e(error, "Login failed")
84+
showToast(getString(R.string.login_failed))
85+
}
86+
}
87+
}
7888

79-
lifecycleScope.launch {
80-
loginViewModel.uiState.collectLatest {
81-
if (!it.error && !it.loading) {
82-
Timber.d(it.toString())
83-
showToast(it.toastMessage)
89+
private fun observeState() {
90+
lifecycleScope.launch {
91+
repeatOnLifecycle(Lifecycle.State.STARTED) {
92+
loginViewModel.uiState.collect { state ->
93+
when (state) {
94+
is UiState.Loading -> showLoading(true)
95+
is UiState.Success -> {
8496
startActivity<MainActivity>()
8597
finishAffinity()
8698
}
99+
else -> {
100+
showLoading(false)
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
108+
private fun observeEvents() {
109+
lifecycleScope.launch {
110+
repeatOnLifecycle(Lifecycle.State.STARTED) {
111+
loginViewModel.uiEvent.collect { event ->
112+
when (event) {
113+
is UiEvent.ShowToast -> showToast(event.message)
87114
}
88115
}
89116
}
90117
}
91118
}
92119

120+
private fun showLoading(isLoading: Boolean) {
121+
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
122+
binding.ibKakaoLogin.visibility = if (isLoading) View.INVISIBLE else View.VISIBLE
123+
}
124+
93125
override fun onBackPressed() {
94126
super.onBackPressed()
95-
finishAffinity()
96-
//탈퇴나 로그아웃 하고 로그인 화면으로 오고, 그 뒤에 뒤로 가기를 눌렀을 때에 백스택 방지
127+
finishAffinity() //로그인 화면에서 뒤로 가기 눌렀을 때에는 백스택 없어야 함 (앱 종료)
97128
}
98-
}
129+
}

0 commit comments

Comments
 (0)