diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f767c1..59f7d49 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -88,4 +88,7 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) + // KaKao + implementation("com.kakao.sdk:v2-user:2.20.1") + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6533a3d..94bbce5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HsLink"> + + - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/core/navigation/AppRoute.kt b/app/src/main/java/com/hsLink/hslink/core/navigation/AppRoute.kt new file mode 100644 index 0000000..0a3bdeb --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/core/navigation/AppRoute.kt @@ -0,0 +1,9 @@ +package com.hsLink.hslink.core.navigation + +import kotlinx.serialization.Serializable + +@Serializable +data object AppMain : Route // 전체 앱의 메인 (기존 MainScreen) + +@Serializable +data object Onboarding : Route \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt b/app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt index bd684e6..d434a4b 100644 --- a/app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt +++ b/app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt @@ -3,6 +3,7 @@ package com.hsLink.hslink.data.di import com.hsLink.hslink.BuildConfig import com.hsLink.hslink.data.service.commuunity.CommunityPostService import com.hsLink.hslink.data.service.home.PostService +import com.hsLink.hslink.data.service.login.AuthService import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import dagger.Module import dagger.Provides @@ -14,7 +15,6 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.Retrofit -import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -65,6 +65,11 @@ object NetworkModule { fun providePostService(retrofit: Retrofit): PostService = retrofit.create(PostService::class.java) + @Provides + @Singleton + fun provideAuthService(retrofit: Retrofit): AuthService { + return retrofit.create(AuthService::class.java) + } @Provides @Singleton fun provideCommunityPostService(retrofit: Retrofit): CommunityPostService = diff --git a/app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt b/app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt index 029efd8..506224b 100644 --- a/app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt @@ -1,15 +1,18 @@ package com.hsLink.hslink.data.di +import com.hsLink.hslink.data.repositoryimpl.AuthRepositoryImpl import com.hsLink.hslink.data.repositoryimpl.CommunityRepositoryImpl import com.hsLink.hslink.data.repositoryimpl.DummyRepositoryImpl import com.hsLink.hslink.data.repositoryimpl.home.PostRepositoryImpl import com.hsLink.hslink.domain.DummyRepository +import com.hsLink.hslink.domain.repository.AuthRepository import com.hsLink.hslink.domain.repository.community.CommunityRepository import com.hsLink.hslink.domain.repository.home.PostRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -25,9 +28,14 @@ interface RepositoryModule { postRepositoryImpl: PostRepositoryImpl, ): PostRepository + @Binds + @Singleton + fun bindAuthRepository( + authRepositoryImpl: AuthRepositoryImpl + ): AuthRepository + @Binds fun bindsCommunityPostRepository( communityPostRepositoryImpl: CommunityRepositoryImpl, ): CommunityRepository - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hsLink/hslink/data/dto/request/SocialLoginRequest.kt b/app/src/main/java/com/hsLink/hslink/data/dto/request/SocialLoginRequest.kt new file mode 100644 index 0000000..4b372a1 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/data/dto/request/SocialLoginRequest.kt @@ -0,0 +1,9 @@ +package com.hsLink.hslink.data.dto.request + +import kotlinx.serialization.Serializable + +@Serializable +data class SocialLoginRequest( + val provider: String, + val accessToken: String +) \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/data/dto/response/SocialLoginResponse.kt b/app/src/main/java/com/hsLink/hslink/data/dto/response/SocialLoginResponse.kt new file mode 100644 index 0000000..42b4fb3 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/data/dto/response/SocialLoginResponse.kt @@ -0,0 +1,11 @@ +package com.hsLink.hslink.data.dto.response + +import kotlinx.serialization.Serializable + +@Serializable +data class SocialLoginResponse( + val accessToken: String, + val refreshToken: String, + val isNewUser: Boolean, + val needsOnboarding: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt b/app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt new file mode 100644 index 0000000..4e46295 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt @@ -0,0 +1,32 @@ +package com.hsLink.hslink.data.repositoryimpl + +import com.hsLink.hslink.data.dto.request.SocialLoginRequest +import com.hsLink.hslink.data.dto.response.SocialLoginResponse +import com.hsLink.hslink.data.service.login.AuthService +import com.hsLink.hslink.domain.repository.AuthRepository +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val authService: AuthService +) : AuthRepository { + + override suspend fun loginWithSocialToken( + provider: String, + accessToken: String + ): Result { + return try { + val request = SocialLoginRequest(provider, accessToken) + val response = authService.socialLogin(request) + + if (response.isSuccessful) { + response.body()?.let { loginResponse -> + Result.success(loginResponse) + } ?: Result.failure(Exception("응답이 비어있습니다")) + } else { + Result.failure(Exception("로그인 실패: ${response.code()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/data/service/login/AuthService.kt b/app/src/main/java/com/hsLink/hslink/data/service/login/AuthService.kt new file mode 100644 index 0000000..0985bf1 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/data/service/login/AuthService.kt @@ -0,0 +1,12 @@ +package com.hsLink.hslink.data.service.login + +import com.hsLink.hslink.data.dto.request.SocialLoginRequest +import com.hsLink.hslink.data.dto.response.SocialLoginResponse +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + @POST("auth/login") + suspend fun socialLogin(@Body request: SocialLoginRequest): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/domain/repository/AuthRepository.kt b/app/src/main/java/com/hsLink/hslink/domain/repository/AuthRepository.kt new file mode 100644 index 0000000..a64fcfe --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/domain/repository/AuthRepository.kt @@ -0,0 +1,10 @@ +package com.hsLink.hslink.domain.repository + +import com.hsLink.hslink.data.dto.response.SocialLoginResponse + +interface AuthRepository { + suspend fun loginWithSocialToken( + provider: String, + accessToken: String + ): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/component/KakaoLoginButton.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/component/KakaoLoginButton.kt new file mode 100644 index 0000000..f30eccd --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/component/KakaoLoginButton.kt @@ -0,0 +1,59 @@ +package com.hsLink.hslink.presentation.login.component + +import android.R.attr.enabled +import androidx.compose.foundation.Image +import androidx.compose.foundation.R +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +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.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun KakaoLoginButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true +) { + Button( + onClick = onClick, + enabled = enabled, + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFFFEE500), // 카카오 노란색 + contentColor = Color.Black // 텍스트 및 아이콘 색상 + ), + shape = RoundedCornerShape(6.dp), // 모서리 둥글기 + modifier = Modifier + .fillMaxWidth(0.85f) // 가로 폭 (조정 가능) + .height(54.dp) // 높이 (Figma 기준) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + // 말풍선 아이콘 (SVG 또는 PNG 리소스) + Image( + painter = painterResource(id = com.hsLink.hslink.R.drawable.ic_kakao_icon), + contentDescription = "카카오 로고", + modifier = Modifier + .size(20.dp) + .padding(end = 8.dp) + ) + Text( + text = "카카오 로그인", + color = Color(0xFF191919), // 카카오 가이드 기준 검정 85% + fontSize = 16.sp + ) + } + } +} diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/navigation/LoginNavigation.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/navigation/LoginNavigation.kt new file mode 100644 index 0000000..f8e43f0 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/navigation/LoginNavigation.kt @@ -0,0 +1,31 @@ +package com.hsLink.hslink.presentation.login.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.hsLink.hslink.core.navigation.Route +import com.hsLink.hslink.presentation.login.screen.KaKaoLoginScreen +import kotlinx.serialization.Serializable + +fun NavController.navigateToLogin(navOptions: NavOptions? = null) { + navigate(Login, navOptions) +} + +fun NavGraphBuilder.loginNavGraph( + padding: PaddingValues, + onNavigateToMain: () -> Unit, + onNavigateToOnboarding: () -> Unit +) { + composable { + KaKaoLoginScreen( + paddingValues = padding, + onNavigateToHome = onNavigateToMain, + onNavigateToOnboarding = onNavigateToOnboarding + ) + } +} + +@Serializable +data object Login : Route // Route 인터페이스 구현 \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/screen/KaKaoLoginScreen.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/screen/KaKaoLoginScreen.kt new file mode 100644 index 0000000..7b9af50 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/screen/KaKaoLoginScreen.kt @@ -0,0 +1,161 @@ +package com.hsLink.hslink.presentation.login.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.hsLink.hslink.core.designsystem.theme.HsLinkTheme +import com.hsLink.hslink.presentation.login.component.KakaoLoginButton +import com.hsLink.hslink.presentation.login.state.KakaoLoginManager +import com.hsLink.hslink.presentation.login.state.LoginState +import com.hsLink.hslink.presentation.login.viewmodel.LoginViewModel + +@Preview(showBackground = true) +@Composable +fun KaKaoLoginScreenPreview() { + KaKaoLoginScreen() +} + + +@Composable +fun KaKaoLoginScreen( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues(0.dp), + viewModel: LoginViewModel = hiltViewModel(), + onNavigateToHome: () -> Unit = {}, + onNavigateToOnboarding: () -> Unit = {} +) { + val context = LocalContext.current + val loginState by viewModel.loginState.collectAsState() + + // 로그인 성공 시 네비게이션 처리 + LaunchedEffect(loginState) { + // 지역 변수로 만들어서 스마트 캐스팅 가능하게 만들기 + val currentState = loginState + when (currentState) { + is LoginState.Success -> { + if (currentState.needsOnboarding) { + onNavigateToOnboarding() + } else { + onNavigateToHome() + } + } + + else -> {} + } + } + + Box( + modifier = modifier + .fillMaxSize() + .background( + brush = Brush.verticalGradient( + colors = listOf( + HsLinkTheme.colors.SkyBlue400, + Color.White + ), + startY = 0f, + endY = Float.POSITIVE_INFINITY + ) + ) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(id = com.hsLink.hslink.R.drawable.img_login_logo), + contentDescription = "앱 로고", + modifier = Modifier.size(213.dp) + ) + Text( + text = "한성인들이 연결되는 곳", + style = HsLinkTheme.typography.title_24Strong, + color = HsLinkTheme.colors.DeepBlue600 + ) + } + + Spacer(modifier = Modifier.padding(115.dp)) + + KakaoLoginButton( + onClick = { + KakaoLoginManager.startKakaoLogin(context) { accessToken, error -> + if (error != null) { + // TODO: 에러 처리 개선 + println("카카오 로그인 실패: ${error.message}") + } else if (accessToken != null) { + // 서버에 토큰 전송 + viewModel.loginWithKakaoToken(accessToken) + } + } + }, + enabled = loginState !is LoginState.Loading + ) + + // 로그인 상태에 따른 UI - 여기도 지역 변수 사용 + val currentLoginState = loginState + when (currentLoginState) { + is LoginState.Loading -> { + Spacer(modifier = Modifier.padding(16.dp)) + CircularProgressIndicator( + color = HsLinkTheme.colors.DeepBlue600 + ) + Text( + text = "로그인 중...", + style = HsLinkTheme.typography.body_14Normal, + color = HsLinkTheme.colors.DeepBlue600, + modifier = Modifier.padding(top = 8.dp) + ) + } + + is LoginState.Error -> { + Spacer(modifier = Modifier.padding(16.dp)) + Text( + text = currentLoginState.message, + color = Color.Red, + style = HsLinkTheme.typography.body_14Normal + ) + } + + is LoginState.Success -> { + Spacer(modifier = Modifier.padding(16.dp)) + Text( + text = "로그인 성공!", + color = Color.Green, + style = HsLinkTheme.typography.body_14Normal + ) + } + + LoginState.Idle -> { + // 아무것도 표시하지 않음 + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/state/KakaoLoginManager.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/state/KakaoLoginManager.kt new file mode 100644 index 0000000..3f75144 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/state/KakaoLoginManager.kt @@ -0,0 +1,41 @@ +package com.hsLink.hslink.presentation.login.state + +import android.content.Context +import com.kakao.sdk.user.UserApiClient + +// 카카오 로그인을 처리하는 도우미 클래스 +object KakaoLoginManager { + + // 카카오 로그인을 시작하는 함수 + fun startKakaoLogin( + context: Context, // 현재 화면 정보 + onResult: (String?, Throwable?) -> Unit // 결과를 받을 함수 + ) { + // 1. 카카오톡이 설치되어 있는지 확인 + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + // 카카오톡으로 로그인 시도 + UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> + if (error != null) { + // 카카오톡 로그인 실패 -> 카카오계정으로 시도 + loginWithKakaoAccount(context, onResult) + } else { + // 카카오톡 로그인 성공 + onResult(token?.accessToken, null) + } + } + } else { + // 카카오톡이 없으면 바로 카카오계정으로 로그인 + loginWithKakaoAccount(context, onResult) + } + } + + // 카카오계정으로 로그인하는 함수 + private fun loginWithKakaoAccount( + context: Context, + onResult: (String?, Throwable?) -> Unit + ) { + UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> + onResult(token?.accessToken, error) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/state/LoginState.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/state/LoginState.kt new file mode 100644 index 0000000..3ee05b9 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/state/LoginState.kt @@ -0,0 +1,14 @@ +package com.hsLink.hslink.presentation.login.state + +// 로그인의 여러 상태를 정의하는 클래스 +sealed class LoginState { + object Idle : LoginState() // 대기중 (아무것도 안 한 상태) + object Loading : LoginState() // 로딩중 (로그인 진행중) + data class Success( // 성공 (로그인 완료) + val isNewUser: Boolean, // 새로운 사용자인지 + val needsOnboarding: Boolean // 온보딩이 필요한지 + ) : LoginState() + data class Error( // 실패 (에러 발생) + val message: String // 에러 메시지 + ) : LoginState() +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/login/viewmodel/LoginViewModel.kt b/app/src/main/java/com/hsLink/hslink/presentation/login/viewmodel/LoginViewModel.kt new file mode 100644 index 0000000..76ff200 --- /dev/null +++ b/app/src/main/java/com/hsLink/hslink/presentation/login/viewmodel/LoginViewModel.kt @@ -0,0 +1,47 @@ +package com.hsLink.hslink.presentation.login.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.hsLink.hslink.domain.repository.AuthRepository +import com.hsLink.hslink.presentation.login.state.LoginState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val authRepository: AuthRepository +) : ViewModel() { + + private val _loginState = MutableStateFlow(LoginState.Idle) + val loginState: StateFlow = _loginState.asStateFlow() + + fun loginWithKakaoToken(accessToken: String) { + _loginState.value = LoginState.Loading + + viewModelScope.launch { + authRepository.loginWithSocialToken("kakao", accessToken) + .onSuccess { response -> + println("서버 로그인 성공: $response") + _loginState.value = LoginState.Success( + isNewUser = response.isNewUser, + needsOnboarding = response.needsOnboarding + ) + } + .onFailure { error -> + println("서버 로그인 실패: ${error.message}") + println("에러 상세: ${error.printStackTrace()}") + _loginState.value = LoginState.Error( + error.message ?: "로그인에 실패했습니다" + ) + } + } + } + + fun resetLoginState() { + _loginState.value = LoginState.Idle + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/main/MainActivity.kt b/app/src/main/java/com/hsLink/hslink/presentation/main/MainActivity.kt index 5353856..cc81aab 100644 --- a/app/src/main/java/com/hsLink/hslink/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/hsLink/hslink/presentation/main/MainActivity.kt @@ -1,21 +1,134 @@ package com.hsLink.hslink.presentation.main +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle +import android.util.Base64 +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController import com.hsLink.hslink.core.designsystem.theme.HsLinkTheme +import com.hsLink.hslink.presentation.login.screen.KaKaoLoginScreen +import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.AndroidEntryPoint +import kotlinx.serialization.Serializable +import java.security.MessageDigest @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // 카카오 SDK 초기화 + KakaoSdk.init(this, "a0bbd28c9384baa131a731d1b914307c") + + // 키 해시 확인 (API 레벨에 따라 다른 방법 사용) + try { + val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) + } else { + @Suppress("DEPRECATION") + packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + } + + val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo.signingInfo?.apkContentsSigners + } else { + @Suppress("DEPRECATION") + packageInfo.signatures + } + + signatures?.let { sigs -> + for (signature in sigs) { + val md = MessageDigest.getInstance("SHA") + md.update(signature.toByteArray()) + val keyHash = Base64.encodeToString(md.digest(), Base64.NO_WRAP) + Log.d("KeyHash", "KeyHash: $keyHash") + println("KeyHash: $keyHash") + } + } + } catch (e: Exception) { + Log.e("KeyHash", "Error getting key hash", e) + } + enableEdgeToEdge() setContent { HsLinkTheme { - MainScreen() + AppNavigation() } } } } + +@Serializable +data object Login + +@Serializable +data object AppMain + +@Composable +private fun AppNavigation() { + val navController = rememberNavController() + var isLoginChecked by remember { mutableStateOf(false) } + var isLoggedIn by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + isLoggedIn = checkLoginStatus() + isLoginChecked = true + } + + if (!isLoginChecked) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + return + } + + NavHost( + navController = navController, + startDestination = if (isLoggedIn) AppMain else Login + ) { + composable { + KaKaoLoginScreen( + paddingValues = PaddingValues(), + onNavigateToHome = { + navController.navigate(AppMain) { + popUpTo(Login) { inclusive = true } + } + }, + onNavigateToOnboarding = { + navController.navigate(AppMain) { + popUpTo(Login) { inclusive = true } + } + } + ) + } + + composable { + MainScreen() + } + } +} + +private suspend fun checkLoginStatus(): Boolean { + return false +} \ No newline at end of file diff --git a/app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt b/app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt index c3a7b47..9958277 100644 --- a/app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt +++ b/app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt @@ -9,6 +9,7 @@ import com.hsLink.hslink.presentation.community.navigation.post.communityPostNav import com.hsLink.hslink.presentation.community.navigation.write.communityWriteNavGraph import com.hsLink.hslink.presentation.home.navigation.homeNavGraph import com.hsLink.hslink.presentation.home.navigation.searchNavGraph +import com.hsLink.hslink.presentation.login.navigation.loginNavGraph import com.hsLink.hslink.presentation.mypage.navigation.main.mypageNavGraph import com.hsLink.hslink.presentation.mypage.navigation.profile.profileEditNavGraph import com.hsLink.hslink.presentation.mypage.navigation.career.careerNavGraph @@ -25,6 +26,13 @@ fun MainNavHost( startDestination = navigator.startDestination, modifier = modifier ) { + + loginNavGraph( + padding = padding, + onNavigateToMain = { navigator.navigateToHome() }, + onNavigateToOnboarding = { navigator.navigateToHome() } + ) + homeNavGraph(padding) searchNavGraph(padding) communityNavGraph( diff --git a/app/src/main/res/drawable/ic_kakao_icon.xml b/app/src/main/res/drawable/ic_kakao_icon.xml new file mode 100644 index 0000000..1f43a04 --- /dev/null +++ b/app/src/main/res/drawable/ic_kakao_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/img_login_logo.png b/app/src/main/res/drawable/img_login_logo.png new file mode 100644 index 0000000..19d21ff Binary files /dev/null and b/app/src/main/res/drawable/img_login_logo.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 9cd993d..852fbc7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,6 +16,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") } } }