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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,7 @@ dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)

// KaKao
implementation("com.kakao.sdk:v2-user:2.20.1")

}
17 changes: 16 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,32 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HsLink">

<!-- 메인 액티비티 (원래 그대로) -->
<activity
android:name=".presentation.main.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.HsLink">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- 카카오 로그인용 별도 액티비티 (새로 추가) -->
<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="kakaoa0bbd28c9384baa131a731d1b914307c" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

님아.

</intent-filter>
</activity>
Comment on lines +31 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Move hardcoded API key to BuildConfig.

The Kakao native app key a0bbd28c9384baa131a731d1b914307c is hardcoded in the manifest (line 39) and also in MainActivity.kt (line 39). Hardcoding API keys in source-controlled files exposes them in version control history and decompiled APKs.

Move the key to local.properties or use BuildConfig:

In build.gradle.kts:

android {
    defaultConfig {
        // Read from local.properties
        val properties = Properties()
        properties.load(project.rootProject.file("local.properties").inputStream())
        val kakaoAppKey = properties.getProperty("kakao.app.key", "")
        
        manifestPlaceholders["kakaoAppKey"] = kakaoAppKey
        buildConfigField("String", "KAKAO_APP_KEY", "\"$kakaoAppKey\"")
    }
}

In AndroidManifest.xml:

-                <data android:host="oauth" android:scheme="kakaoa0bbd28c9384baa131a731d1b914307c" />
+                <data android:host="oauth" android:scheme="kakao${kakaoAppKey}" />

In MainActivity.kt:

-        KakaoSdk.init(this, "a0bbd28c9384baa131a731d1b914307c")
+        KakaoSdk.init(this, BuildConfig.KAKAO_APP_KEY)

In local.properties (not committed):

kakao.app.key=a0bbd28c9384baa131a731d1b914307c
🤖 Prompt for AI Agents
In app/src/main/AndroidManifest.xml around lines 31 to 41 the Kakao native app
key is hardcoded in the activity data scheme; move this secret to a build-time
property and use BuildConfig/manifestPlaceholder instead. Add code in
build.gradle.kts to read kakao.app.key from local.properties, set
manifestPlaceholders["kakaoAppKey"] and a
buildConfigField("String","KAKAO_APP_KEY",...) so the key is injected at build
time; update AndroidManifest.xml to reference the placeholder for the scheme
(use the manifest placeholder variable instead of the literal key); update
MainActivity.kt to read the key from BuildConfig.KAKAO_APP_KEY (not a hardcoded
string); add the key to local.properties (do NOT commit local.properties) and
ensure any existing hardcoded occurrences are removed or replaced.


</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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
7 changes: 6 additions & 1 deletion app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 =
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -25,9 +28,14 @@ interface RepositoryModule {
postRepositoryImpl: PostRepositoryImpl,
): PostRepository

@Binds
@Singleton
fun bindAuthRepository(
authRepositoryImpl: AuthRepositoryImpl
): AuthRepository
Comment on lines +33 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix method naming to match existing pattern.

The method name should be bindsAuthRepository (with 's') to maintain consistency with the other binding methods in this module: bindsDummyRepository, bindsPostRepository, and bindsCommunityPostRepository.

Apply this diff to fix the naming:

-    fun bindAuthRepository(
+    fun bindsAuthRepository(
         authRepositoryImpl: AuthRepositoryImpl
     ): AuthRepository
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun bindAuthRepository(
authRepositoryImpl: AuthRepositoryImpl
): AuthRepository
fun bindsAuthRepository(
authRepositoryImpl: AuthRepositoryImpl
): AuthRepository
🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt around lines
33 to 35, the binder method is named bindAuthRepository but should follow the
module's naming convention and be renamed to bindsAuthRepository; update the
function name from bindAuthRepository to bindsAuthRepository (including any
references/usages if present) so it matches bindsDummyRepository,
bindsPostRepository, and bindsCommunityPostRepository and keeps consistency
across the DI module.


@Binds
fun bindsCommunityPostRepository(
communityPostRepositoryImpl: CommunityRepositoryImpl,
): CommunityRepository

}
}
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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<SocialLoginResponse> {
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<SocialLoginResponse>
}
Original file line number Diff line number Diff line change
@@ -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<SocialLoginResponse>
}
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Login> {
KaKaoLoginScreen(
paddingValues = padding,
onNavigateToHome = onNavigateToMain,
onNavigateToOnboarding = onNavigateToOnboarding
)
}
}

@Serializable
data object Login : Route // Route 인터페이스 구현
Loading