diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d600c635..bf2077f2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,14 +27,21 @@ android { "BASE_URL", gradleLocalProperties(rootDir).getProperty("base.url"), ) + buildConfigField( + "String", + "NATIVE_APP_KEY", + gradleLocalProperties(rootDir).getProperty("native.app.key"), + ) + manifestPlaceholders["NATIVE_APP_KEY"] = + gradleLocalProperties(rootDir).getProperty("native.app.key") } buildTypes { release { isMinifyEnabled = false proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", ) } } @@ -95,4 +102,8 @@ dependencies { implementation(timber) implementation(ossLicense) } -} \ No newline at end of file + + KakaoDependencies.run { + implementation(user) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f2fb6c8..cd351e75 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,21 @@ android:usesCleartextTraffic="true" tools:targetApi="31"> + + + + + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/java/com/going/going/MyApp.kt b/app/src/main/java/com/going/going/MyApp.kt index 766cf55b..6ac307d0 100644 --- a/app/src/main/java/com/going/going/MyApp.kt +++ b/app/src/main/java/com/going/going/MyApp.kt @@ -2,6 +2,7 @@ package com.going.going import android.app.Application import androidx.appcompat.app.AppCompatDelegate +import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.HiltAndroidApp import timber.log.Timber @@ -13,6 +14,7 @@ class MyApp : Application() { initTimber() setDayMode() + initKakaoSdk() } private fun initTimber() { @@ -23,4 +25,7 @@ class MyApp : Application() { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } -} \ No newline at end of file + private fun initKakaoSdk() { + KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index b5df03ba..42b23205 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,4 +14,4 @@ buildscript { tasks.register("clean", Delete::class) { delete(rootProject.buildDir) -} \ No newline at end of file +} diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index b8d6f325..3eb9818f 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -5,4 +5,4 @@ object Constants { const val targetSdk = 34 const val versionCode = 1 const val versionName = "1.0" -} \ No newline at end of file +} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index d8043bfa..597f044a 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -92,4 +92,8 @@ object FirebaseDependencies { const val crashlytics = "com.google.firebase:firebase-crashlytics-ktx" const val analytics = "com.google.firebase:firebase-analytics-ktx" const val remoteConfig = "com.google.firebase:firebase-config-ktx" -} \ No newline at end of file +} + +object KakaoDependencies { + const val user = "com.kakao.sdk:v2-user:${Versions.kakaoVersion}" +} diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 151e8489..d31682bb 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -33,7 +33,7 @@ object Versions { const val balloonVersion = "1.4.5" const val lottieVersion = "6.0.0" const val circularProgressBar = "3.1.0" - const val kakaoVersion = "2.14.0" + const val kakaoVersion = "2.19.0" const val circleIndicatorVersion = "2.1.6" const val shimmerVersion = "0.5.0" const val navigationVersion = "2.6.0" @@ -44,4 +44,4 @@ object Versions { val javaVersion = JavaVersion.VERSION_17 const val jvmVersion = "17" -} \ No newline at end of file +} diff --git a/domain/src/main/kotlin/com/going/domain/entity/response/AuthTokenModel.kt b/domain/src/main/kotlin/com/going/domain/entity/response/AuthTokenModel.kt new file mode 100644 index 00000000..38fe35f9 --- /dev/null +++ b/domain/src/main/kotlin/com/going/domain/entity/response/AuthTokenModel.kt @@ -0,0 +1,7 @@ +package com.going.domain.entity.response + +data class AuthTokenModel( + val isResigned: Boolean, + val accessToken: String, + val refreshToken: String, +) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 72e712f4..02222dac 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Tue Dec 26 03:22:09 KST 2023 +#Fri Dec 29 23:45:17 KST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 391da731..fa4be555 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -84,4 +84,8 @@ dependencies { implementation(circularProgressBar) implementation(circleIndicator) } -} \ No newline at end of file + + KakaoDependencies.run { + implementation(user) + } +} diff --git a/presentation/src/main/java/com/going/presentation/auth/LoginActivity.kt b/presentation/src/main/java/com/going/presentation/auth/LoginActivity.kt new file mode 100644 index 00000000..32c405e7 --- /dev/null +++ b/presentation/src/main/java/com/going/presentation/auth/LoginActivity.kt @@ -0,0 +1,63 @@ +package com.going.presentation.auth + +import android.os.Bundle +import androidx.activity.viewModels +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.going.presentation.R +import com.going.presentation.databinding.ActivityLoginBinding +import com.going.ui.base.BaseActivity +import com.going.ui.extension.UiState +import com.going.ui.extension.setOnSingleClickListener +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class LoginActivity : BaseActivity(R.layout.activity_login) { + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initKakaoLoginBtnClickListener() + observeInfo() + } + + private fun initKakaoLoginBtnClickListener() { + binding.btnSignIn.setOnSingleClickListener { + viewModel.startKakaoLogIn(this) + } + } + + private fun observeInfo() { + observeIsAppLoginAvailable() + observePostChangeTokenState() + } + + private fun observeIsAppLoginAvailable() { + viewModel.isAppLoginAvailable.flowWithLifecycle(lifecycle).onEach { canLogin -> + if (!canLogin) viewModel.startKakaoLogIn(this) + }.launchIn(lifecycleScope) + } + + private fun observePostChangeTokenState() { + viewModel.postChangeTokenState.flowWithLifecycle(lifecycle).onEach { tokenState -> + when (tokenState) { + is UiState.Success -> { + // 성공 했을 때 로직 + } + + is UiState.Failure -> { + // 실패 했을 때 로직 + } + + is UiState.Empty -> { + // 여튼 로직 + } + + is UiState.Loading -> { + // 로딩 중 로직 + } + } + }.launchIn(lifecycleScope) + } +} diff --git a/presentation/src/main/java/com/going/presentation/auth/LoginViewModel.kt b/presentation/src/main/java/com/going/presentation/auth/LoginViewModel.kt new file mode 100644 index 00000000..54fd0a35 --- /dev/null +++ b/presentation/src/main/java/com/going/presentation/auth/LoginViewModel.kt @@ -0,0 +1,82 @@ +package com.going.presentation.auth + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.going.domain.entity.response.AuthTokenModel +import com.going.ui.extension.UiState +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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class LoginViewModel : ViewModel() { + private val _postChangeTokenState = MutableStateFlow>(UiState.Empty) + val postChangeTokenState: StateFlow> = _postChangeTokenState + + private val _isAppLoginAvailable = MutableStateFlow(true) + val isAppLoginAvailable: StateFlow = _isAppLoginAvailable + + private var webLoginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error -> + if (error == null && token != null) { + changeTokenFromServer( + accessToken = token.accessToken, + ) + } + } + + private var appLoginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error -> + if (error != null) { + // 뒤로가기 경우 예외 처리 + if (!(error is ClientError && error.reason == ClientErrorCause.Cancelled)) { + _isAppLoginAvailable.value = false + } + } else if (token != null) { + changeTokenFromServer( + accessToken = token.accessToken, + ) + } + } + + fun startKakaoLogIn(context: Context) { + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context) && isAppLoginAvailable.value) { + UserApiClient.instance.loginWithKakaoTalk( + context = context, + callback = appLoginCallback, + ) + } else { + UserApiClient.instance.loginWithKakaoAccount( + context = context, + callback = webLoginCallback, + ) + } + } + + // 서버통신 - 카카오 토큰 보내서 서비스 토큰 받아오기 - 서버와 협의 후 수정예정 + private fun changeTokenFromServer( + accessToken: String, + social: String = KAKAO, + ) { + _postChangeTokenState.value = UiState.Loading + + viewModelScope.launch { + // 통신 로직 + + // 성공시 서버에서 준 정보를 넣는 예시 코드 + _postChangeTokenState.value = UiState.Success( + AuthTokenModel( + isResigned = true, + accessToken = "testAccessToekn", + refreshToken = "testRefreshToekn", + ), + ) + } + } + + companion object { + const val KAKAO = "KAKAO" + } +} diff --git a/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt b/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt index b34761d9..f418928c 100644 --- a/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt +++ b/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt @@ -58,5 +58,4 @@ class MockActivity() : BaseActivity(R.layout.activity_mock) super.onDestroy() _adapter = null } - -} \ No newline at end of file +} diff --git a/presentation/src/main/res/drawable/img_sign_in_kakao_button.png b/presentation/src/main/res/drawable/img_sign_in_kakao_button.png new file mode 100644 index 00000000..d94ea5d9 Binary files /dev/null and b/presentation/src/main/res/drawable/img_sign_in_kakao_button.png differ diff --git a/presentation/src/main/res/drawable/img_sign_in_main.png b/presentation/src/main/res/drawable/img_sign_in_main.png new file mode 100644 index 00000000..159aa2eb Binary files /dev/null and b/presentation/src/main/res/drawable/img_sign_in_main.png differ diff --git a/presentation/src/main/res/layout/activity_login.xml b/presentation/src/main/res/layout/activity_login.xml new file mode 100644 index 00000000..090f1f13 --- /dev/null +++ b/presentation/src/main/res/layout/activity_login.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index af32c4f5..4aa2c76e 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -8,4 +8,7 @@ 서버 통신에 실패했습니다. + 여행을 시작해보세요 + 개인정보처리방침 + diff --git a/settings.gradle.kts b/settings.gradle.kts index fac4e87a..b7b7240e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,9 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + + // KakaoSDK repository + maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/") } }