Skip to content

Commit 3e9517f

Browse files
authored
Merge pull request #14 from YAPP-Github/feature/#5-kakao-login
2 parents 1d93680 + 679e736 commit 3e9517f

13 files changed

Lines changed: 213 additions & 20 deletions

File tree

.github/workflows/develop_branch.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ jobs:
1414
- name: check out repository
1515
uses: actions/checkout@v4
1616

17+
- name: Generate local.properties
18+
env:
19+
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
20+
run: |
21+
echo "kakao.native.app.key=$KAKAO_NATIVE_APP_KEY" >> local.properties
22+
1723
- name: set up JDK 17
1824
uses: actions/setup-java@v4
1925
with:

app/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.util.Properties
2+
13
plugins {
24
alias(libs.plugins.bitnagil.android.application)
35
alias(libs.plugins.bitnagil.android.hilt)
@@ -7,8 +9,32 @@ plugins {
79
android {
810
namespace = "com.threegap.bitnagil"
911

12+
val properties =
13+
Properties().apply {
14+
val propFile = rootProject.file("local.properties")
15+
if (propFile.exists()) {
16+
load(propFile.inputStream())
17+
}
18+
}
19+
1020
defaultConfig {
1121
applicationId = "com.threegap.bitnagil"
22+
23+
val kakaoNativeAppKey =
24+
(properties["kakao.native.app.key"] as? String)
25+
?: System.getenv("KAKAO_NATIVE_APP_KEY")
26+
?: throw GradleException("KAKAO_NATIVE_APP_KEY 값이 없습니다.")
27+
28+
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = kakaoNativeAppKey
29+
buildConfigField(
30+
type = "String",
31+
name = "KAKAO_NATIVE_APP_KEY",
32+
value = "\"$kakaoNativeAppKey\"",
33+
)
34+
}
35+
36+
buildFeatures {
37+
buildConfig = true
1238
}
1339
}
1440

@@ -20,4 +46,5 @@ dependencies {
2046
implementation(projects.data)
2147
implementation(projects.domain)
2248
implementation(projects.presentation)
49+
implementation(libs.kakao.v2.user)
2350
}

app/src/main/AndroidManifest.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@
2525
<category android:name="android.intent.category.LAUNCHER" />
2626
</intent-filter>
2727
</activity>
28-
</application>
2928

29+
<activity
30+
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
31+
android:exported="true">
32+
<intent-filter>
33+
<action android:name="android.intent.action.VIEW" />
34+
35+
<category android:name="android.intent.category.DEFAULT" />
36+
<category android:name="android.intent.category.BROWSABLE" />
37+
38+
<data
39+
android:host="oauth"
40+
android:scheme="kakao${KAKAO_NATIVE_APP_KEY}" />
41+
</intent-filter>
42+
</activity>
43+
</application>
3044
</manifest>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package com.threegap.bitnagil
22

33
import android.app.Application
4+
import com.kakao.sdk.common.KakaoSdk
45
import dagger.hilt.android.HiltAndroidApp
56

67
@HiltAndroidApp
78
class BitnagilApplication : Application() {
89
override fun onCreate() {
910
super.onCreate()
11+
initKakaoSdk()
12+
}
13+
14+
private fun initKakaoSdk() {
15+
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
1016
}
1117
}

app/src/main/java/com/threegap/bitnagil/MainNavHost.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier
55
import androidx.navigation.compose.NavHost
66
import androidx.navigation.compose.composable
77
import com.threegap.bitnagil.presentation.home.HomeScreen
8-
import com.threegap.bitnagil.presentation.login.LoginScreen
8+
import com.threegap.bitnagil.presentation.login.LoginScreenContainer
99

1010
@Composable
1111
fun MainNavHost(
@@ -18,9 +18,7 @@ fun MainNavHost(
1818
modifier = modifier,
1919
) {
2020
composable<Route.Login> {
21-
LoginScreen(
22-
onLoginClick = { navigator.navController.navigate(Route.Home) },
23-
)
21+
LoginScreenContainer()
2422
}
2523

2624
composable<Route.Home> {

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ espressoCore = "3.6.1"
5050
material = "1.12.0"
5151
orbit = "6.1.0"
5252
javax = "1"
53+
kakaoLogin = "2.21.4"
5354

5455
[libraries]
5556
## Android Gradle Plugin
@@ -115,6 +116,7 @@ kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coro
115116

116117
## Other
117118
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
119+
kakao-v2-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoLogin" }
118120

119121
[bundles]
120122
androidx-core = [

presentation/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313

1414
implementation(libs.bundles.androidx.core)
1515
implementation(libs.bundles.orbit)
16+
implementation(libs.kakao.v2.user)
1617

1718
testImplementation(libs.junit)
1819
testImplementation(libs.kotlin.coroutines.test)
Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,81 @@
11
package com.threegap.bitnagil.presentation.login
22

33
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.layout.Arrangement
5-
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.Spacer
4+
import androidx.compose.foundation.layout.Box
75
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.height
6+
import androidx.compose.foundation.layout.padding
97
import androidx.compose.material3.Button
108
import androidx.compose.material3.Text
119
import androidx.compose.runtime.Composable
1210
import androidx.compose.ui.Alignment
1311
import androidx.compose.ui.Modifier
1412
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.platform.LocalContext
1514
import androidx.compose.ui.tooling.preview.Preview
1615
import androidx.compose.ui.unit.dp
16+
import androidx.hilt.navigation.compose.hiltViewModel
17+
import com.kakao.sdk.user.UserApiClient
1718
import com.threegap.bitnagil.designsystem.BitnagilTheme
19+
import com.threegap.bitnagil.presentation.login.model.LoginIntent
20+
import com.threegap.bitnagil.presentation.login.model.LoginSideEffect
21+
import org.orbitmvi.orbit.compose.collectSideEffect
1822

1923
@Composable
20-
fun LoginScreen(
21-
onLoginClick: () -> Unit,
24+
fun LoginScreenContainer(viewModel: LoginViewModel = hiltViewModel()) {
25+
val context = LocalContext.current
26+
val client = UserApiClient.instance
27+
28+
viewModel.collectSideEffect { sideEffect ->
29+
when (sideEffect) {
30+
is LoginSideEffect.RequestKakaoTalkLogin -> {
31+
client.loginWithKakaoTalk(context) { token, error ->
32+
viewModel.sendIntent(LoginIntent.OnKakaoLoginResult(token, error))
33+
}
34+
}
35+
36+
is LoginSideEffect.RequestKakaoAccountLogin -> {
37+
client.loginWithKakaoAccount(context) { token, error ->
38+
viewModel.sendIntent(LoginIntent.OnKakaoLoginResult(token, error))
39+
}
40+
}
41+
}
42+
}
43+
44+
LoginScreen(
45+
onKakaoLoginClick = {
46+
viewModel.sendIntent(
47+
LoginIntent.OnKakaoLoginClick(client.isKakaoTalkLoginAvailable(context)),
48+
)
49+
},
50+
)
51+
}
52+
53+
@Composable
54+
private fun LoginScreen(
55+
onKakaoLoginClick: () -> Unit,
2256
modifier: Modifier = Modifier,
2357
) {
24-
Column(
25-
verticalArrangement = Arrangement.Center,
26-
horizontalAlignment = Alignment.CenterHorizontally,
58+
Box(
2759
modifier =
2860
modifier
2961
.fillMaxSize()
3062
.background(Color.White),
3163
) {
32-
Text(text = "여긴 로그인 화면")
33-
34-
Spacer(modifier = Modifier.height(10.dp))
64+
Text(
65+
text = "빛나길 로고",
66+
modifier = Modifier.align(Alignment.Center),
67+
)
3568

3669
Button(
37-
onClick = onLoginClick,
70+
onClick = onKakaoLoginClick,
71+
modifier =
72+
Modifier
73+
.align(Alignment.BottomCenter)
74+
.padding(20.dp),
3875
) {
39-
Text("로그인 버튼 눌러보던가")
76+
Text(
77+
text = "카카오 로그인버튼",
78+
)
4079
}
4180
}
4281
}
@@ -46,7 +85,7 @@ fun LoginScreen(
4685
private fun LoginScreenPreview() {
4786
BitnagilTheme {
4887
LoginScreen(
49-
onLoginClick = {},
88+
onKakaoLoginClick = {},
5089
)
5190
}
5291
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.threegap.bitnagil.presentation.login
2+
3+
import android.util.Log
4+
import androidx.lifecycle.SavedStateHandle
5+
import com.kakao.sdk.common.model.ClientError
6+
import com.kakao.sdk.common.model.ClientErrorCause
7+
import com.kakao.sdk.user.UserApiClient
8+
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
9+
import com.threegap.bitnagil.presentation.login.model.LoginIntent
10+
import com.threegap.bitnagil.presentation.login.model.LoginSideEffect
11+
import com.threegap.bitnagil.presentation.login.model.LoginState
12+
import dagger.hilt.android.lifecycle.HiltViewModel
13+
import org.orbitmvi.orbit.syntax.simple.SimpleSyntax
14+
import javax.inject.Inject
15+
16+
@HiltViewModel
17+
class LoginViewModel
18+
@Inject
19+
constructor(
20+
private val savedStateHandle: SavedStateHandle,
21+
) : MviViewModel<LoginState, LoginSideEffect, LoginIntent>(
22+
initState = LoginState(),
23+
savedStateHandle = savedStateHandle,
24+
) {
25+
override suspend fun SimpleSyntax<LoginState, LoginSideEffect>.reduceState(
26+
intent: LoginIntent,
27+
state: LoginState,
28+
): LoginState? =
29+
when (intent) {
30+
is LoginIntent.OnKakaoLoginClick -> {
31+
if (!intent.onKakaoTalkLoginAvailable) {
32+
sendSideEffect(LoginSideEffect.RequestKakaoAccountLogin)
33+
} else {
34+
sendSideEffect(LoginSideEffect.RequestKakaoTalkLogin)
35+
}
36+
null
37+
}
38+
39+
is LoginIntent.OnKakaoLoginResult -> {
40+
when {
41+
intent.token != null -> {
42+
Log.i("KakaoLogin", "로그인 성공 ${intent.token.accessToken}")
43+
UserApiClient.instance.me { user, error ->
44+
if (error != null) {
45+
Log.e("KakaoLogin", "사용자 정보 요청 실패", error)
46+
} else if (user != null) {
47+
Log.i(
48+
"KakaoLogin",
49+
"사용자 정보 요청 성공" +
50+
"\n이메일: ${user.kakaoAccount?.email}" +
51+
"\n닉네임: ${user.kakaoAccount?.profile?.nickname}",
52+
)
53+
}
54+
}
55+
}
56+
57+
intent.error is ClientError && intent.error.reason == ClientErrorCause.Cancelled -> {
58+
Log.e("KakaoLogin", "로그인 취소", intent.error)
59+
}
60+
61+
intent.error != null -> {
62+
Log.e("KakaoLogin", "로그인 실패", intent.error)
63+
}
64+
}
65+
null
66+
}
67+
}
68+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.threegap.bitnagil.presentation.login.model
2+
3+
import com.kakao.sdk.auth.model.OAuthToken
4+
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent
5+
6+
sealed class LoginIntent : MviIntent {
7+
data class OnKakaoLoginClick(val onKakaoTalkLoginAvailable: Boolean) : LoginIntent()
8+
9+
data class OnKakaoLoginResult(
10+
val token: OAuthToken?,
11+
val error: Throwable?,
12+
) : LoginIntent()
13+
}

0 commit comments

Comments
 (0)