Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refact/kakao_login]: 카카오 로그인 코드 리팩토링 #77

Merged
merged 15 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ android {
dependencies {

// Feature
implementation(projects.feature.statistics)
implementation(projects.feature.login)
implementation(projects.feature.onboarding)
implementation(projects.feature.main)
Expand Down
21 changes: 9 additions & 12 deletions app/src/main/java/com/hmh/hamyeonham/SampleActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.hmh.hamyeonham.common.view.viewBinding
import com.hmh.hamyeonham.databinding.ActivitySampleBinding
import com.hmh.hamyeonham.feature.main.MainActivity
import com.hmh.hamyeonham.feature.login.LoginActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
Expand All @@ -21,8 +21,7 @@ class SampleActivity : AppCompatActivity() {
val splashScreen = installSplashScreen()
initSplashAnimation(splashScreen)
setContentView(binding.root)
startActivity(Intent(this, MainActivity::class.java))
// startActivity(Intent(this, StaticsActivity::class.java))
startActivity(Intent(this, LoginActivity::class.java))
finish()
}

Expand All @@ -31,16 +30,14 @@ class SampleActivity : AppCompatActivity() {
val splashScreenView = splashScreenViewProvider.view
val fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out)

fadeOut.setAnimationListener(
object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
splashScreenViewProvider.remove()
}
fadeOut.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
splashScreenViewProvider.remove()
}

override fun onAnimationRepeat(animation: Animation) {}
},
)
override fun onAnimationRepeat(animation: Animation) {}
})
splashScreenView.startAnimation(fadeOut)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,18 @@ import android.content.Context
import android.content.Intent
import com.hmh.hamyeonham.common.navigation.NavigationProvider
import com.hmh.hamyeonham.feature.login.LoginActivity
import com.hmh.hamyeonham.feature.login.UserInfoActivity
import com.hmh.hamyeonham.feature.main.MainActivity
import com.hmh.hamyeonham.feature.onboarding.OnBoardingActivity
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class DefaultNavigationProvider @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
) : NavigationProvider {
override fun toOnboarding(): Intent {
override fun toOnBoarding(): Intent {
return Intent(context, OnBoardingActivity::class.java)
}

override fun toLogin(): Intent {
return Intent(context, LoginActivity::class.java)
}

override fun toUserInfo(): Intent {
return Intent(context, UserInfoActivity::class.java)
}

override fun toMain(): Intent {
return Intent(context, MainActivity::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package com.hmh.hamyeonham.common.navigation
import android.content.Intent

interface NavigationProvider {
fun toOnboarding(): Intent
fun toOnBoarding(): Intent
fun toLogin(): Intent
fun toUserInfo(): Intent

fun toMain(): Intent
}
2 changes: 2 additions & 0 deletions feature/login/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ dependencies {

// dot indicator
implementation(libs.dot.indicator)
implementation(projects.core.designsystem)

}
3 changes: 0 additions & 3 deletions feature/login/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name=".UserInfoActivity"
android:exported="false" />
<activity
android:name=".LoginActivity"
android:exported="false" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,98 +1,66 @@
package com.hmh.hamyeonham.feature.login

import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.hmh.hamyeonham.common.context.toast
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.hmh.hamyeonham.common.navigation.NavigationProvider
import com.hmh.hamyeonham.feature.login.data.DummyImage
import com.hmh.hamyeonham.common.view.viewBinding
import com.hmh.hamyeonham.feature.login.databinding.ActivityLoginBinding
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject

@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding

private val loginViewModel: LoginViewModel by viewModels()
private val binding by viewBinding(ActivityLoginBinding::inflate)
private val viewModel by viewModels<LoginViewModel>()
private lateinit var loginViewPagerAdapter: LoginViewPagerAdapter

@Inject
lateinit var navigationProvider: NavigationProvider

// 삭제 예정
private val dummyImageList = listOf(
DummyImage(
Image = R.drawable.login_sample_rectagle_viewpager,
),
DummyImage(
Image = R.drawable.login_sample_rectagle_viewpager,
),
DummyImage(
Image = R.drawable.login_sample_rectagle_viewpager,
),
)

private val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
when {
error != null -> {
}

token != null -> {
moveToMainActivity()
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityLoginBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding.root)

binding.btnLogin.setOnClickListener {
loginWithKakaoApp()
binding.ivKakaoLogin.setOnClickListener {
viewModel.loginWithKakaoApp(this)
}
setLoginViewPager()
handleKakaoLoginSuccess()
}

private fun handleKakaoLoginSuccess() {
viewModel.kakaoLoginState.flowWithLifecycle(lifecycle).onEach { state ->
if (state.isSuccessResult) {
viewModel.getKakaoUserNickname()
// 닉네임 출력
Log.d("LoginActivity", "닉네임: ${state.kakaoNickname}")
moveToOnBoardingActivity()
}
}.launchIn(lifecycleScope)
}

private fun setLoginViewPager() {
loginViewPagerAdapter = LoginViewPagerAdapter(dummyImageList)
val loginViewImageList = listOf(
R.drawable.login_sample_rectagle_viewpager,
R.drawable.login_sample_rectagle_viewpager,
R.drawable.login_sample_rectagle_viewpager,
)

loginViewPagerAdapter = LoginViewPagerAdapter(loginViewImageList)
binding.run {
vpLogin.adapter = loginViewPagerAdapter
indicatorLoginDots.attachTo(binding.vpLogin)
}
}

private fun loginWithKakaoApp() {
if (UserApiClient.instance.isKakaoTalkLoginAvailable(this)) {
UserApiClient.instance.loginWithKakaoTalk(this) { token, error ->
if (error != null) {
toast("카카오 로그인 실패")
if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
toast("다시 로그인 해주세요.")
return@loginWithKakaoTalk
}
loginWithKakaoAccount()
} else if (token != null) {
toast("카카오 로그인 성공")
moveToMainActivity()
}
}
} else {
loginWithKakaoAccount()
}
}

private fun loginWithKakaoAccount() {
UserApiClient.instance.loginWithKakaoAccount(this, callback = callback)
}

private fun moveToMainActivity() {
startActivity(navigationProvider.toMain())
private fun moveToOnBoardingActivity() {
startActivity(navigationProvider.toOnBoarding())
finish()
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,90 @@
package com.hmh.hamyeonham.feature.login

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import android.content.Context
import androidx.lifecycle.ViewModel
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject

class LoginViewModel : ViewModel(){
@HiltViewModel
class LoginViewModel @Inject constructor() : ViewModel() {
data class KakaoLoginState(
val isSuccessResult: Boolean = false,
val accessToken: String? = null,
val refreshToken: String? = null,
val kakaoNickname: String? = null,
)

private val _kakaoLoginResult = MutableLiveData<Boolean>()
val kakaoLoginResult: LiveData<Boolean> get() = _kakaoLoginResult
private val _kakaoLoginState = MutableStateFlow(KakaoLoginState())
val kakaoLoginState = _kakaoLoginState.asStateFlow()

private fun updateState(transform: KakaoLoginState.() -> KakaoLoginState) {
val currentState = kakaoLoginState.value
val newState = currentState.transform()
_kakaoLoginState.value = newState
}

fun loginWithKakaoApp(context: Context) {
if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
if (error != null) {
if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
return@loginWithKakaoTalk
}
loginWithKakaoAccount(context)
} else if (token != null) {
updateState {
copy(
isSuccessResult = true,
accessToken = token.accessToken,
refreshToken = token.refreshToken,
)
}
}
}
} else {
loginWithKakaoAccount(context)
}
}

private fun loginWithKakaoAccount(context: Context) {
UserApiClient.instance.loginWithKakaoAccount(context) { token, error ->
if (error != null) {
updateState {
copy(
isSuccessResult = false,
accessToken = "",
refreshToken = "",
)
}
} else if (token != null) {
updateState {
copy(
isSuccessResult = true,
accessToken = token.accessToken,
refreshToken = token.refreshToken,
)
}
}
}
}

fun getKakaoUserNickname() {
UserApiClient.instance.me { user, error ->
if (error != null) {
// 닉네임 정보 얻기 실패 시
} else if (user != null) {
val kakaoNickname = user.kakaoAccount?.profile?.nickname
updateState {
copy(
kakaoNickname = kakaoNickname,
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.hmh.hamyeonham.feature.login.data.DummyImage
import com.hmh.hamyeonham.feature.login.databinding.ItemLoginViewPagerBinding

class LoginViewPagerAdapter(private val imageList: List<DummyImage>) :
class LoginViewPagerAdapter(private val imageList: List<Int>) :
RecyclerView.Adapter<LoginViewPagerAdapter.PagerViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
Expand All @@ -19,9 +18,9 @@ class LoginViewPagerAdapter(private val imageList: List<DummyImage>) :
class PagerViewHolder(private val binding: ItemLoginViewPagerBinding) :
RecyclerView.ViewHolder(binding.root) {

fun onBindView(imageInfo: DummyImage) {
fun onBindView(imageInfo: Int) {
binding.run {
ivLoginViewPagerItem.load(imageInfo.Image) {
ivLoginViewPagerItem.load(imageInfo) {
placeholder(R.drawable.login_sample_rectagle_viewpager)
error(R.drawable.login_sample_rectagle_viewpager)
}
Expand Down
Loading