Skip to content
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
6 changes: 6 additions & 0 deletions .github/workflows/develop_branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ jobs:
- name: check out repository
uses: actions/checkout@v4

- name: Generate local.properties
env:
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
run: |
echo "kakao.native.app.key=$KAKAO_NATIVE_APP_KEY" >> local.properties
- name: set up JDK 17
uses: actions/setup-java@v4
with:
Expand Down
27 changes: 27 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.Properties

plugins {
alias(libs.plugins.bitnagil.android.application)
alias(libs.plugins.bitnagil.android.hilt)
Expand All @@ -7,8 +9,32 @@ plugins {
android {
namespace = "com.threegap.bitnagil"

val properties =
Properties().apply {
val propFile = rootProject.file("local.properties")
if (propFile.exists()) {
load(propFile.inputStream())
}
}

defaultConfig {
applicationId = "com.threegap.bitnagil"

val kakaoNativeAppKey =
(properties["kakao.native.app.key"] as? String)
?: System.getenv("KAKAO_NATIVE_APP_KEY")
?: throw GradleException("KAKAO_NATIVE_APP_KEY 값이 없습니다.")

manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = kakaoNativeAppKey
buildConfigField(
type = "String",
name = "KAKAO_NATIVE_APP_KEY",
value = "\"$kakaoNativeAppKey\"",
)
}

buildFeatures {
buildConfig = true
}
}

Expand All @@ -19,4 +45,5 @@ dependencies {
implementation(projects.data)
implementation(projects.domain)
implementation(projects.presentation)
implementation(libs.kakao.v2.user)
}
16 changes: 15 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="oauth"
android:scheme="kakao${KAKAO_NATIVE_APP_KEY}" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.threegap.bitnagil

import android.app.Application
import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class BitnagilApplication : Application() {
override fun onCreate() {
super.onCreate()
initKakaoSdk()
}

private fun initKakaoSdk() {
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
}
}
6 changes: 2 additions & 4 deletions app/src/main/java/com/threegap/bitnagil/MainNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.threegap.bitnagil.presentation.home.HomeScreen
import com.threegap.bitnagil.presentation.login.LoginScreen
import com.threegap.bitnagil.presentation.login.LoginScreenContainer

@Composable
fun MainNavHost(
Expand All @@ -18,9 +18,7 @@ fun MainNavHost(
modifier = modifier,
) {
composable<Route.Login> {
LoginScreen(
onLoginClick = { navigator.navController.navigate(Route.Home) },
)
LoginScreenContainer()
}

composable<Route.Home> {
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ espressoCore = "3.6.1"
material = "1.12.0"
orbit = "6.1.0"
javax = "1"
kakaoLogin = "2.21.4"

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

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

[bundles]
androidx-core = [
Expand Down
1 change: 1 addition & 0 deletions presentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {

implementation(libs.bundles.androidx.core)
implementation(libs.bundles.orbit)
implementation(libs.kakao.v2.user)

testImplementation(libs.junit)
testImplementation(libs.kotlin.coroutines.test)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,81 @@
package com.threegap.bitnagil.presentation.login

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.kakao.sdk.user.UserApiClient
import com.threegap.bitnagil.designsystem.BitnagilTheme
import com.threegap.bitnagil.presentation.login.model.LoginIntent
import com.threegap.bitnagil.presentation.login.model.LoginSideEffect
import org.orbitmvi.orbit.compose.collectSideEffect

@Composable
fun LoginScreen(
onLoginClick: () -> Unit,
fun LoginScreenContainer(viewModel: LoginViewModel = hiltViewModel()) {
val context = LocalContext.current
val client = UserApiClient.instance

viewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
is LoginSideEffect.RequestKakaoTalkLogin -> {
client.loginWithKakaoTalk(context) { token, error ->
viewModel.sendIntent(LoginIntent.OnKakaoLoginResult(token, error))
}
}

is LoginSideEffect.RequestKakaoAccountLogin -> {
client.loginWithKakaoAccount(context) { token, error ->
viewModel.sendIntent(LoginIntent.OnKakaoLoginResult(token, error))
}
}
}
}

LoginScreen(
onKakaoLoginClick = {
viewModel.sendIntent(
LoginIntent.OnKakaoLoginClick(client.isKakaoTalkLoginAvailable(context)),
)
},
)
}

@Composable
private fun LoginScreen(
onKakaoLoginClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
Box(
modifier =
modifier
.fillMaxSize()
.background(Color.White),
) {
Text(text = "여긴 로그인 화면")

Spacer(modifier = Modifier.height(10.dp))
Text(
text = "빛나길 로고",
modifier = Modifier.align(Alignment.Center),
)

Button(
onClick = onLoginClick,
onClick = onKakaoLoginClick,
modifier =
Modifier
.align(Alignment.BottomCenter)
.padding(20.dp),
) {
Text("로그인 버튼 눌러보던가")
Text(
text = "카카오 로그인버튼",
)
}
}
}
Expand All @@ -46,7 +85,7 @@ fun LoginScreen(
private fun LoginScreenPreview() {
BitnagilTheme {
LoginScreen(
onLoginClick = {},
onKakaoLoginClick = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.threegap.bitnagil.presentation.login

import android.util.Log
Comment thread
l5x5l marked this conversation as resolved.
import androidx.lifecycle.SavedStateHandle
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
import com.threegap.bitnagil.presentation.login.model.LoginIntent
import com.threegap.bitnagil.presentation.login.model.LoginSideEffect
import com.threegap.bitnagil.presentation.login.model.LoginState
import dagger.hilt.android.lifecycle.HiltViewModel
import org.orbitmvi.orbit.syntax.simple.SimpleSyntax
import javax.inject.Inject

@HiltViewModel
class LoginViewModel
@Inject
constructor(
private val savedStateHandle: SavedStateHandle,
) : MviViewModel<LoginState, LoginSideEffect, LoginIntent>(
initState = LoginState(),
savedStateHandle = savedStateHandle,
) {
override suspend fun SimpleSyntax<LoginState, LoginSideEffect>.reduceState(
intent: LoginIntent,
state: LoginState,
): LoginState? =
when (intent) {
is LoginIntent.OnKakaoLoginClick -> {
if (!intent.onKakaoTalkLoginAvailable) {
sendSideEffect(LoginSideEffect.RequestKakaoAccountLogin)
} else {
sendSideEffect(LoginSideEffect.RequestKakaoTalkLogin)
}
null
}

is LoginIntent.OnKakaoLoginResult -> {
when {
intent.token != null -> {
Log.i("KakaoLogin", "로그인 성공 ${intent.token.accessToken}")
UserApiClient.instance.me { user, error ->
if (error != null) {
Log.e("KakaoLogin", "사용자 정보 요청 실패", error)
} else if (user != null) {
Log.i(
"KakaoLogin",
"사용자 정보 요청 성공" +
"\n이메일: ${user.kakaoAccount?.email}" +
"\n닉네임: ${user.kakaoAccount?.profile?.nickname}",
)
}
}
}

intent.error is ClientError && intent.error.reason == ClientErrorCause.Cancelled -> {
Log.e("KakaoLogin", "로그인 취소", intent.error)
}

intent.error != null -> {
Log.e("KakaoLogin", "로그인 실패", intent.error)
Comment thread
l5x5l marked this conversation as resolved.
}
}
null
}
Comment thread
l5x5l marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.threegap.bitnagil.presentation.login.model

import com.kakao.sdk.auth.model.OAuthToken
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent

sealed class LoginIntent : MviIntent {
data class OnKakaoLoginClick(val onKakaoTalkLoginAvailable: Boolean) : LoginIntent()

data class OnKakaoLoginResult(
val token: OAuthToken?,
val error: Throwable?,
) : LoginIntent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.threegap.bitnagil.presentation.login.model

import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect

sealed interface LoginSideEffect : MviSideEffect {
data object RequestKakaoTalkLogin : LoginSideEffect

data object RequestKakaoAccountLogin : LoginSideEffect
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.threegap.bitnagil.presentation.login.model

import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState
import kotlinx.parcelize.Parcelize

@Parcelize
data class LoginState(
val isLoggedIn: Boolean = false,
) : MviState
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") }
}
}

Expand Down