diff --git a/.github/workflows/android-pull-request-ci.yml b/.github/workflows/android-pull-request-ci.yml
index bc6da2c7..c9bccc6a 100644
--- a/.github/workflows/android-pull-request-ci.yml
+++ b/.github/workflows/android-pull-request-ci.yml
@@ -22,8 +22,10 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- - name: Create local.properties with base.url
- run: echo "base.url=https://ci-placeholder.local" >> local.properties
+ - name: Create local.properties with base.url and kakao.native.key
+ run: |
+ echo "base.url=https://ci-placeholder.local" >> local.properties
+ echo "kakao.native.key=${{ secrets.KAKAO_NATIVE_KEY }}" >> local.properties
- name: Run ktlint
run: ./gradlew ktlintCheck
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 243cc8bf..c6c1b976 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -21,4 +21,6 @@ dependencies {
implementation(projects.remote)
implementation(projects.domain)
implementation(projects.feature.main)
+
+ implementation(libs.kakao.login)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f164c8a9..4f824c48 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,5 +17,19 @@
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/teamsolply/solply/SolplyApplication.kt b/app/src/main/java/com/teamsolply/solply/SolplyApplication.kt
index 37ac4a92..1241e236 100644
--- a/app/src/main/java/com/teamsolply/solply/SolplyApplication.kt
+++ b/app/src/main/java/com/teamsolply/solply/SolplyApplication.kt
@@ -1,11 +1,15 @@
package com.teamsolply.solply
import android.app.Application
+import com.kakao.sdk.common.KakaoSdk
+import com.teamsolply.solply.buildconfig.BuildConfig.KAKAO_NATIVE_KEY
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class SolplyApplication : Application() {
override fun onCreate() {
super.onCreate()
+
+ KakaoSdk.init(this, KAKAO_NATIVE_KEY)
}
}
diff --git a/build-logic/convention/src/main/java/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationPlugin.kt
index ec23dabd..d2fce12c 100644
--- a/build-logic/convention/src/main/java/AndroidApplicationPlugin.kt
+++ b/build-logic/convention/src/main/java/AndroidApplicationPlugin.kt
@@ -1,4 +1,5 @@
import com.android.build.api.dsl.ApplicationExtension
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import com.teamsolply.solply.convention.configureAndroidCompose
import com.teamsolply.solply.convention.configureKotlinAndroid
import com.teamsolply.solply.convention.extension.getLibrary
@@ -24,6 +25,7 @@ internal class AndroidApplicationPlugin : Plugin {
targetSdk = libs.getVersion("targetSdk").requiredVersion.toInt()
versionCode = libs.getVersion("versionCode").requiredVersion.toInt()
versionName = libs.getVersion("versionName").requiredVersion
+ manifestPlaceholders["KAKAO_NATIVE_KEY"] = gradleLocalProperties(rootDir, providers).getProperty("kakao.native.key").replace("\"", "")
}
}
diff --git a/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt b/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt
index d62eddd9..50c5a73d 100644
--- a/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt
+++ b/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt
@@ -14,6 +14,11 @@ internal fun Project.configureBuildConfig(
"BASE_URL",
gradleLocalProperties(rootDir, providers).getProperty("base.url")
)
+ buildConfigField(
+ "String",
+ "KAKAO_NATIVE_KEY",
+ gradleLocalProperties(rootDir, providers).getProperty("kakao.native.key")
+ )
}
buildFeatures {
diff --git a/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt b/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt
index 7b8134b7..79f45e18 100644
--- a/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt
+++ b/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt
@@ -1,6 +1,7 @@
package com.teamsolply.solply.buildconfig.impl
-import com.teamsolply.solply.buildconfig.BuildConfig
+import com.teamsolply.solply.buildconfig.BuildConfig.BASE_URL
+import com.teamsolply.solply.buildconfig.BuildConfig.KAKAO_NATIVE_KEY
import com.teamsolply.solply.common.buildconfig.BuildConfigFieldProvider
import com.teamsolply.solply.common.buildconfig.BuildConfigFields
import javax.inject.Inject
@@ -8,7 +9,8 @@ import javax.inject.Inject
class BuildConfigFieldsProviderImpl @Inject constructor() : BuildConfigFieldProvider {
override fun get(): BuildConfigFields =
BuildConfigFields(
- baseUrl = BuildConfig.BASE_URL,
+ baseUrl = BASE_URL,
+ kakaoNativeKey = KAKAO_NATIVE_KEY,
isDebug = true
)
}
diff --git a/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt b/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt
index 7ee2225d..4676c9ee 100644
--- a/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt
+++ b/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt
@@ -2,5 +2,6 @@ package com.teamsolply.solply.common.buildconfig
data class BuildConfigFields(
val baseUrl: String,
+ val kakaoNativeKey: String,
val isDebug: Boolean
)
diff --git a/core/ui/src/main/java/com/teamsolply/solply/ui/lifecycle/LaunchedEffectWithLifecycle.kt b/core/ui/src/main/java/com/teamsolply/solply/ui/lifecycle/LaunchedEffectWithLifecycle.kt
new file mode 100644
index 00000000..4d37a187
--- /dev/null
+++ b/core/ui/src/main/java/com/teamsolply/solply/ui/lifecycle/LaunchedEffectWithLifecycle.kt
@@ -0,0 +1,24 @@
+package com.teamsolply.solply.ui.lifecycle
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.CoroutineScope
+
+@Composable
+fun LaunchedEffectWithLifecycle(
+ key1: Any? = Unit,
+ key2: Any? = Unit,
+ lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+ minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+ action: suspend CoroutineScope.() -> Unit = {}
+) {
+ LaunchedEffect(key1 = key1, key2 = key2) {
+ lifecycleOwner.lifecycle.repeatOnLifecycle(minActiveState) {
+ action()
+ }
+ }
+}
diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts
index 668e0f65..e420306b 100644
--- a/feature/main/build.gradle.kts
+++ b/feature/main/build.gradle.kts
@@ -7,5 +7,6 @@ android {
}
dependencies {
+ implementation(projects.feature.oauth)
implementation(projects.feature.home)
}
diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt
index 9e18cdd9..7a81d09f 100644
--- a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt
+++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt
@@ -9,8 +9,8 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
-import com.teamsolply.solply.home.navigation.Home
import com.teamsolply.solply.home.navigation.navigateHome
+import com.teamsolply.solply.oauth.navigation.Oauth
internal class MainNavigator(
val navController: NavHostController
@@ -19,7 +19,7 @@ internal class MainNavigator(
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
- val startDestination = Home
+ val startDestination = Oauth
val currentTab: MainNavTab?
@Composable get() = MainNavTab.find { tab ->
diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt
index 24391ed9..ab50be6b 100644
--- a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt
+++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt
@@ -14,6 +14,7 @@ import androidx.navigation.compose.NavHost
import com.teamsolply.solply.designsystem.theme.SolplyTheme
import com.teamsolply.solply.home.navigation.homeNavGraph
import com.teamsolply.solply.main.component.MainBottomBar
+import com.teamsolply.solply.oauth.navigation.oauthNavGraph
import kotlinx.collections.immutable.toPersistentList
@Composable
@@ -33,6 +34,7 @@ internal fun MainScreen(
.background(color = SolplyTheme.colors.white)
.fillMaxSize()
) {
+ oauthNavGraph(paddingValues = innerPadding)
homeNavGraph(paddingValues = innerPadding)
}
},
diff --git a/feature/oauth/.gitignore b/feature/oauth/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/oauth/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/oauth/build.gradle.kts b/feature/oauth/build.gradle.kts
new file mode 100644
index 00000000..75fcbced
--- /dev/null
+++ b/feature/oauth/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ alias(libs.plugins.solply.feature)
+}
+
+android {
+ namespace = "com.teamsolply.solply.oauth"
+}
+
+dependencies {
+ implementation(libs.kakao.login)
+}
diff --git a/feature/oauth/src/main/AndroidManifest.xml b/feature/oauth/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/feature/oauth/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt
new file mode 100644
index 00000000..2ad000fc
--- /dev/null
+++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/navigation/OauthNavigation.kt
@@ -0,0 +1,29 @@
+package com.teamsolply.solply.oauth.navigation
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.teamsolply.solply.navigation.Route
+import com.teamsolply.solply.oauth.presentation.OauthRoute
+import kotlinx.serialization.Serializable
+
+fun NavController.navigateOauth(
+ navOptions: NavOptions
+) {
+ navigate(Oauth, navOptions)
+}
+
+fun NavGraphBuilder.oauthNavGraph(
+ paddingValues: PaddingValues
+) {
+ composable {
+ OauthRoute(
+ paddingValues = paddingValues
+ )
+ }
+}
+
+@Serializable
+data object Oauth : Route
diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthContract.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthContract.kt
new file mode 100644
index 00000000..028732d9
--- /dev/null
+++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthContract.kt
@@ -0,0 +1,17 @@
+package com.teamsolply.solply.oauth.presentation
+
+import com.teamsolply.solply.ui.base.SideEffect
+import com.teamsolply.solply.ui.base.UiIntent
+import com.teamsolply.solply.ui.base.UiState
+
+data class OauthState(
+ val g: String = ""
+) : UiState
+
+sealed interface OauthIntent : UiIntent {
+ data object KakaoLoginClick : OauthIntent
+}
+
+sealed interface OauthSideEffect : SideEffect {
+ data object StartKakaoLogin : OauthSideEffect
+}
diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthScreen.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthScreen.kt
new file mode 100644
index 00000000..4e3fae43
--- /dev/null
+++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthScreen.kt
@@ -0,0 +1,123 @@
+package com.teamsolply.solply.oauth.presentation
+
+import android.app.Activity
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.kakao.sdk.common.model.ClientError
+import com.kakao.sdk.common.model.ClientErrorCause
+import com.kakao.sdk.user.UserApiClient
+import com.teamsolply.solply.ui.extension.customClickable
+import com.teamsolply.solply.ui.lifecycle.LaunchedEffectWithLifecycle
+import kotlinx.coroutines.flow.collectLatest
+
+@Composable
+fun OauthRoute(
+ paddingValues: PaddingValues,
+ viewModel: OauthViewModel = hiltViewModel()
+) {
+ val context = LocalContext.current
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ LaunchedEffectWithLifecycle {
+ viewModel.sideEffect.collectLatest { sideEffect ->
+ when (sideEffect) {
+ OauthSideEffect.StartKakaoLogin -> startKakaoLogin(
+ context = context,
+ onSuccess = { accessToken, refreshToken ->
+ Toast.makeText(context, "로그인 성공", Toast.LENGTH_SHORT).show()
+ Log.d(
+ "asdasdasd",
+ "accessToken: ${accessToken}\n refreshToken: $refreshToken"
+ )
+ },
+ onFailure = { error ->
+ Log.d("asdasdasd", error.toString())
+ }
+ )
+ }
+ }
+ }
+
+ OauthScreen(
+ kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) }
+ )
+}
+
+@Composable
+fun OauthScreen(
+ kakaoLoginClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Oauth",
+ modifier = Modifier.customClickable(
+ onClick = kakaoLoginClick
+ )
+ )
+ }
+}
+
+fun startKakaoLogin(
+ context: Context,
+ onSuccess: (accessToken: String, refreshToken: String?) -> Unit = { _, _ -> },
+ onFailure: (Throwable) -> Unit = {}
+) {
+ val activity = context as? Activity ?: return
+
+ if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
+ UserApiClient.instance.loginWithKakaoTalk(activity) { token, error ->
+ if (error != null) {
+ if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
+ Toast.makeText(context, "로그인 취소", Toast.LENGTH_SHORT).show()
+ return@loginWithKakaoTalk
+ }
+ UserApiClient.instance.loginWithKakaoAccount(context) { accountToken, accountError ->
+ if (accountError != null) {
+ onFailure(accountError)
+ Toast.makeText(
+ context,
+ "로그인 실패: ${accountError.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ return@loginWithKakaoAccount
+ }
+ if (accountToken != null) {
+ onSuccess(accountToken.accessToken, accountToken.refreshToken)
+ }
+ }
+ } else if (token != null) {
+ onSuccess(token.accessToken, token.refreshToken)
+ }
+ }
+ } else {
+ UserApiClient.instance.loginWithKakaoAccount(context) { token, error ->
+ Log.d("KAKAO_LOGIN", "loginWithKakaoAccount called. token=$token, error=$error")
+ if (error != null) {
+ onFailure(error)
+ Toast.makeText(context, "로그인 실패: ${error.message}", Toast.LENGTH_SHORT).show()
+ return@loginWithKakaoAccount
+ }
+ if (token != null) {
+ onSuccess(token.accessToken, token.refreshToken)
+ }
+ }
+ }
+}
diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthViewModel.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthViewModel.kt
new file mode 100644
index 00000000..993de13f
--- /dev/null
+++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/presentation/OauthViewModel.kt
@@ -0,0 +1,15 @@
+package com.teamsolply.solply.oauth.presentation
+
+import com.teamsolply.solply.ui.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class OauthViewModel @Inject constructor() :
+ BaseViewModel(OauthState()) {
+ override fun handleIntent(intent: OauthIntent) {
+ when (intent) {
+ OauthIntent.KakaoLoginClick -> postSideEffect(OauthSideEffect.StartKakaoLogin)
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ead2ff9f..6b97480d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -18,6 +18,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/")
}
}
@@ -37,3 +38,4 @@ include(":local")
include(":remote")
include(":feature:main")
include(":feature:home")
+include(":feature:oauth")