Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
84cd648
setting/#43: 네비게이션3 의존성 추가
choi-day Feb 21, 2026
858f529
feature/#43: RootNavigation 구현
choi-day Feb 24, 2026
9fc2f94
feature/#43: ScreenRoute 구현
choi-day Feb 24, 2026
6a8b863
feature/#43: 신규 유저 판별 및 화면 이동을 위한 RootViewModel구현
choi-day Feb 24, 2026
2e49569
refactor/#43: 신규 유저 판별 상태관리 분리
choi-day Feb 24, 2026
1fa7360
feature/#43: 로그인 네비게이션과 뷰모델 결합 바인더 구현
choi-day Feb 24, 2026
fe5025c
refactor/#43: MainActivity에서 네비게이션 분리
choi-day Feb 24, 2026
044611c
add/#43: 바텀네비게이션 홈 아이콘 추가
choi-day Feb 24, 2026
0ac173f
add/#43: BottomNavRoute 분리
choi-day Feb 24, 2026
e0ab41e
chore/#43: RootNavRoute 파일명 수정
choi-day Feb 24, 2026
79dd31c
refactor/#43: bottomNavRoot분리
choi-day Feb 24, 2026
9f50908
chore/#43: RootNavRoute 파일명 수정
choi-day Feb 24, 2026
d004f5e
chore/#43: RootNavRoute 파일명 수정
choi-day Feb 24, 2026
1b81b92
feature/#43: haze사용을 위한 의존성 추가
choi-day Feb 24, 2026
a874723
feature/#43: 하단 네비게이션바 아이템 정의
choi-day Feb 24, 2026
44efa64
feature/#43: 하단 네비게이션바 UI구현
choi-day Feb 24, 2026
db68676
feature/#43: 네비게이션바 및 화면 전환 구현
choi-day Feb 24, 2026
020bf5e
feature/#43: 메인 네비게이션 상태 관리 구현
choi-day Feb 24, 2026
72c514a
feature/#43: 선택된 아이템에 border 구현
choi-day Feb 24, 2026
15a80c1
add#43: 네비게이션 아이콘 추가
choi-day Mar 8, 2026
25480ac
feat#43: 네비게이션바 프리뷰 생성
choi-day Mar 8, 2026
bf2979e
refactor#43: 불필요한 공백 삭제
choi-day Mar 8, 2026
aab7fba
refactor#43: 패키지 선언 누락 수정
choi-day Mar 8, 2026
6ad95bd
merge#43: dev브랜치 merge
choi-day Mar 8, 2026
e6da2b6
merge#43: dev브랜치 merge
choi-day Mar 8, 2026
f08db34
refactor#43: ktlint수정사항 적용
choi-day Mar 8, 2026
ee6c5bd
refactor#43: 네비게이션 아이콘 변경
choi-day Mar 8, 2026
d6d8885
refactor#43: 네비게이션 애니메이션 제거
choi-day Mar 8, 2026
7948742
Merge branch 'dev' into feature/#43-navigation
choi-day Mar 21, 2026
87f561a
refactor#43: selectableGroup추가
choi-day Mar 21, 2026
412d523
refactor#43: 로그인 실패시 로그인 루트 재시작 구현
choi-day Mar 21, 2026
4820d8d
refactor#43: lifecycleViewmodelNav3 안정 버전으로 수정
choi-day Mar 21, 2026
c4c6b9b
refactor#43: ktlint 적용
choi-day Mar 21, 2026
2636171
refactor#43: glass effect 수정
choi-day Mar 22, 2026
eac4c12
refactor#43: glass effect mainScreen에서 분리
choi-day Mar 22, 2026
a66ccd7
refactor#43: ktlintFormat 적용
choi-day Mar 22, 2026
765da06
refactor#43: 파일 위치 수정
choi-day Mar 22, 2026
16fb2a7
refactor#43: 주석 제거
choi-day Mar 22, 2026
1ea7d77
refactor#43: 주석 제거
choi-day Mar 22, 2026
0597bb7
refactor#43: 파일 위치 변경
choi-day Mar 22, 2026
ea7ec3c
refactor#43: 로그인 ui state 분리
choi-day Mar 22, 2026
3f6db65
refactor#43: ktlint 적용
choi-day Mar 22, 2026
08b4897
refactor#43: box에 높이 추가
choi-day Mar 22, 2026
0e22ed9
refactor#43: 로그인 state와 event분리
choi-day Mar 22, 2026
9e5f56a
merge#43: dev 브랜치 변경사항 반영
choi-day Mar 29, 2026
243621c
refactor#43: sideeffect 사용
choi-day Mar 29, 2026
6740d0b
refactor#43: sideeffect 분리
choi-day Mar 29, 2026
b4aa253
refactor#43: route 수정
choi-day Mar 29, 2026
0295297
refactor#43: 패딩값 조정
choi-day Mar 29, 2026
ffec8ec
refactor#43: 변수명 수정
choi-day Mar 29, 2026
1cc091b
refactor#43: 변수명 수정
choi-day Mar 29, 2026
fd97cfd
refactor#43: ktlint적용
choi-day Mar 29, 2026
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 app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ dependencies {
// 카카오 로그인
implementation("com.kakao.sdk:v2-user:2.20.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)

implementation(libs.haze)
}

ktlint {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gildongmu.dduru.presentation.feature.login
package com.gildongmu.dduru.presentation.feature.login.screen

import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand All @@ -16,22 +17,60 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.gildongmu.dduru.R
import com.gildongmu.dduru.core.designsystem.component.LoginButton
import com.gildongmu.dduru.core.designsystem.theme.DduruTheme
import com.gildongmu.dduru.presentation.feature.login.util.LoginSideEffect
import com.gildongmu.dduru.presentation.feature.login.viewmodel.LoginViewModel
import kotlinx.coroutines.flow.collectLatest

@Composable
fun LoginScreen(
fun LoginRoute(
onLoginSuccess: (Boolean) -> Unit,
onGuestClick: () -> Unit,
viewModel: LoginViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current

LaunchedEffect(Unit) {
viewModel.sideEffect.collectLatest { event ->
when (event) {
is LoginSideEffect.LoginSuccess -> {
onLoginSuccess(event.isNewUser)
}
is LoginSideEffect.ShowError -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
}
}
}

LoginScreen(
isLoading = uiState.isLoading,
onKakaoClick = { viewModel.startKakaoLogin() },
onGoogleClick = { viewModel.startGoogleLogin(context) },
onGuestClick = onGuestClick
)
}

@Composable
private fun LoginScreen(
isLoading: Boolean,
onKakaoClick: () -> Unit,
onGoogleClick: () -> Unit,
Expand Down Expand Up @@ -90,13 +129,11 @@ fun LoginScreen(

Spacer(modifier = Modifier.height(95.dp))

// 2. 버튼 영역
Column(
modifier = Modifier.width(343.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 카카오 로그인
LoginButton(
iconResId = R.drawable.img_kakao_icon,
text = "카카오로 시작하기",
Expand All @@ -105,7 +142,6 @@ fun LoginScreen(
onClick = onKakaoClick
)

// 구글 로그인
LoginButton(
iconResId = R.drawable.img_google_icon,
text = "구글로 시작하기",
Expand All @@ -118,7 +154,6 @@ fun LoginScreen(

Spacer(modifier = Modifier.height(25.dp))

// 3. 가입 없이 둘러볼래요
Text(
text = "가입 없이 둘러볼래요",
modifier = Modifier.clickable { onGuestClick() },
Expand All @@ -137,7 +172,7 @@ fun LoginScreen(
}
}

@Preview(showBackground = true, device = "spec:width=375dp,height=812dp")
@Preview(showBackground = true)
@Composable
private fun LoginScreenPreview() {
DduruTheme {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.gildongmu.dduru.presentation.feature.login
package com.gildongmu.dduru.presentation.feature.login.screen

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand All @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -42,14 +41,11 @@ fun WelcomeScreen(
.padding(top = 100.dp, bottom = 40.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val bubbleShape = RoundedCornerShape(24.dp)
// 1. 말풍선 영역
Box(
modifier = Modifier
.width(340.dp)
.wrapContentHeight()
) {
// 그림자
Image(
painter = painterResource(id = R.drawable.img_message),
contentDescription = null,
Expand All @@ -61,7 +57,7 @@ fun WelcomeScreen(
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(Color.Black)
)
// 원본 이미지

Image(
painter = painterResource(id = R.drawable.img_message),
contentDescription = "뚜르에 오신 것을 환영해요! ...",
Expand All @@ -73,12 +69,10 @@ fun WelcomeScreen(
}
Spacer(modifier = Modifier.height(20.dp))

// 2. 캐릭터 이미지
Box(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier.width(180.dp)
) {
// 그림자
Box(
modifier = Modifier
.padding(bottom = 12.dp)
Expand Down Expand Up @@ -106,7 +100,6 @@ fun WelcomeScreen(
}
Spacer(modifier = Modifier.height(83.dp))

// 4. 버튼
DduruButton(
text = "네, 좋아요!",
onClick = onNextClick,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gildongmu.dduru.presentation.feature.login.util

sealed interface LoginSideEffect {
data class LoginSuccess(val isNewUser: Boolean) : LoginSideEffect
data class ShowError(val message: String) : LoginSideEffect
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이렇게 LoginUiState만든것처럼 SideEffect도 새로운 파일에 만들어주세용 ~

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

수정했습니다

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gildongmu.dduru.presentation.feature.login.util

data class LoginUiState(
val isLoading: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.gildongmu.dduru.presentation.feature.login.viewmodel

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gildongmu.dduru.data.remote.datasource.KakaoAuthDataSource
import com.gildongmu.dduru.domain.usecase.LoginGoogleUseCase
import com.gildongmu.dduru.domain.usecase.LoginKakaoUseCase
import com.gildongmu.dduru.presentation.feature.login.util.LoginSideEffect
import com.gildongmu.dduru.presentation.feature.login.util.LoginUiState
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginGoogleUseCase: LoginGoogleUseCase,
private val loginKakaoUseCase: LoginKakaoUseCase,
private val kakaoAuthDataSource: KakaoAuthDataSource
) : ViewModel() {

private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()

private val _sideEffect = MutableSharedFlow<LoginSideEffect>()
val sideEffect: SharedFlow<LoginSideEffect> = _sideEffect.asSharedFlow()

fun startGoogleLogin(context: Context) {
viewModelScope.launch {
updateLoading(true)
try {
val result = loginGoogleUseCase(context)

result.onSuccess { isNewUser ->
_sideEffect.emit(LoginSideEffect.LoginSuccess(isNewUser))
}.onFailure { e ->
Log.e("LoginViewModel", "Google Login failed: ${e.message}", e)
_sideEffect.emit(LoginSideEffect.ShowError("구글 로그인에 실패했습니다. 다시 시도해주세요."))
}
} finally {
updateLoading(false)
}
}
}

fun startKakaoLogin() {
viewModelScope.launch {
updateLoading(true)
try {
val idToken = kakaoAuthDataSource.getIdToken()
if (idToken.isNullOrBlank()) {
_sideEffect.emit(LoginSideEffect.ShowError("카카오 로그인에 실패했습니다. 다시 시도해주세요."))
return@launch
}

loginKakao(idToken)
} finally {
updateLoading(false)
}
}
}

fun loginWithKakao(idToken: String) {
viewModelScope.launch {
updateLoading(true)
try {
loginKakao(idToken)
} finally {
updateLoading(false)
}
}
}
Comment on lines +70 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: loginWithKakao 함수의 사용처 검색
# Expect: 호출처가 있거나 향후 사용 예정임을 확인

rg -n "loginWithKakao" --type-add 'kotlin:*.kt' --type kotlin

Repository: ddu-ru/ddu-ru-android

Length of output: 2137


🏁 Script executed:

# Search for LoginViewModel references and usage
rg -n "LoginViewModel" --type-add 'kotlin:*.kt' --type kotlin -A 3 -B 1 | head -100

# Also search for any call patterns that might invoke loginWithKakao
rg -n "\.loginWithKakao\(" --type-add 'kotlin:*.kt' --type kotlin

Repository: ddu-ru/ddu-ru-android

Length of output: 3935


🏁 Script executed:

# View LoginScreen.kt to see actual method calls
cat -n app/src/main/java/com/gildongmu/dduru/presentation/feature/login/screen/LoginScreen.kt | grep -A 10 -B 10 -i "kakao\|loginWithKakao\|startKakaoLogin"

Repository: ddu-ru/ddu-ru-android

Length of output: 2593


loginWithKakao 메서드는 사용되지 않으므로 제거하세요.

UI 레이어에서 실제 사용되는 카카오 로그인 진입점은 startKakaoLogin() (라인 31)이며, loginWithKakao()는 어떤 곳에서도 호출되지 않습니다. 외부 idToken 전달용 public 메서드로 의도된 것으로 보이지만 현재 사용처가 없으므로 제거를 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/gildongmu/dduru/presentation/feature/login/viewmodel/LoginViewModel.kt`
around lines 70 - 79, The method loginWithKakao is unused and should be removed;
delete the public function loginWithKakao(idToken: String) from LoginViewModel
and any related tests or callers, leaving startKakaoLogin() as the single Kakao
login entrypoint; if the intention was to keep an external idToken entrypoint,
instead convert loginWithKakao to a private helper used by startKakaoLogin or
document/implement its usage, but otherwise remove the unused public method to
avoid dead code.


private suspend fun loginKakao(idToken: String) {
val result = loginKakaoUseCase(idToken)

result.onSuccess { isNewUser ->
_sideEffect.emit(LoginSideEffect.LoginSuccess(isNewUser))
}.onFailure { e ->
Log.e("LoginViewModel", "Kakao Login failed: ${e.message}", e)
_sideEffect.emit(LoginSideEffect.ShowError("카카오 로그인에 실패했습니다. 다시 시도해주세요."))
}
}

private fun updateLoading(isLoading: Boolean) {
_uiState.value = _uiState.value.copy(isLoading = isLoading)
}
}
Loading
Loading