Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
01849f0
[design] #80 로그인 관련 리소스 추가
Ojongseok Feb 7, 2026
71e90ac
[chore] #80 불필요한 파일 제거
Ojongseok Feb 7, 2026
4d2e936
[feat] #80 로그인 화면 UI 구성
Ojongseok Feb 7, 2026
670da70
[design] #80 약관 관련 리소스 추가
Ojongseok Feb 7, 2026
b8d4320
Merge branch 'develop' of https://github.com/YAPP-Github/27th-App-Tea…
Ojongseok Feb 7, 2026
307b523
Merge branch 'develop' of https://github.com/YAPP-Github/27th-App-Tea…
Ojongseok Feb 7, 2026
765f121
[feat] #80 AuthNavKey 정의 및 RootNavkey 네이밍 Login->Auth로 변경
Ojongseok Feb 7, 2026
5e216e2
[feat] #80 Auth 관련 NavigationState, Navigator 모듈 작성
Ojongseok Feb 7, 2026
f94b703
[feat] #80 AuthNavKey 하위 스크린 생성
Ojongseok Feb 7, 2026
a216be2
[design] #80 28.sp 폰트 시스템 추가
Ojongseok Feb 7, 2026
367b11c
[design] #80 온보딩 및 로그인 UI 컴포넌트 분리
Ojongseok Feb 7, 2026
12420c8
[design] #80 스플래시 UI 컴포넌트 분리
Ojongseok Feb 7, 2026
b9543ed
[chore] #80 불필요한 import 및 파일 제거
Ojongseok Feb 7, 2026
6b2c7c7
[chore] #80 온보딩 관련 상수 경로 이동
Ojongseok Feb 7, 2026
c8e074a
[feat] #80 온보딩 HorizontalPager 오버스크롤 가능하게 수정
Ojongseok Feb 7, 2026
609a3d9
[design] #80 온보딩 스크린 UI 영역 비율 수정
Ojongseok Feb 7, 2026
86cc639
[design] #80 온보딩 관련 리소스 추가
Ojongseok Feb 7, 2026
6687332
[feat] #80 이용약관 화면 NavKey 추가
Ojongseok Feb 7, 2026
7f70c98
[feat] #80 이용약관 관련 컴포넌트 정의 및 UI 구성
Ojongseok Feb 7, 2026
4dccd87
[feat] #80 소셜 로그인 후 이용약관으로 이동하도록 변경
Ojongseok Feb 7, 2026
bc79992
[feat] #80 feature:auth:impl의 공통 Contact 및 ViewModel Auth로 네이밍 변경
Ojongseok Feb 7, 2026
0e46751
[chore] #80 Screen 및 Preview 접근제어자 변경
Ojongseok Feb 7, 2026
fd9a1b4
[feat] #80 이용약관 선택동의 및 전체동의 기능 구현
Ojongseok Feb 7, 2026
f295034
[feat] #80 스플래시 및 온보딩에서 화면 전환 시 백스택 클리어 후 전환하도록 변경
Ojongseok Feb 8, 2026
f95ac0e
[feat] #80 온보딩 노출 여부 확인 로직 추가
Ojongseok Feb 8, 2026
9394b8c
[feat] #80 스플래시 관련 로직 별도 ViewModel로 분리
Ojongseok Feb 8, 2026
57fbb32
[chore] #80 공백 문자 수정
Ojongseok Feb 8, 2026
6f2d35a
[feat] #80 이용약관 화면 이탈 시 약관 동의 상태 초기화 로직 작성
Ojongseok Feb 8, 2026
decdcbe
[fix] #80 소셜 로그인 후 실제 로그인 처리 시점 변경
Ojongseok Feb 8, 2026
48d25b4
[chore] #80 스플래시 / 로그인 ViewModel 공유하는 로직 제거
Ojongseok Feb 8, 2026
e602136
[chore] #80 Auth 관련 클래스명 Login로 변경
Ojongseok Feb 8, 2026
73338ef
[chore] #80 불필요한 코드 제거 및 접근제어자 수정
Ojongseok Feb 8, 2026
2f1845a
[build] #80 detekt 룰 적용
Ojongseok Feb 8, 2026
b7705c6
[feat] #80 이용약관 동의 후 로딩 효과 추가
Ojongseok Feb 8, 2026
19acf32
[feat] #80 회원탈퇴 시 온보딩 노출 여부 false로 변경
Ojongseok Feb 8, 2026
8c80120
[build] #80 불필요한 import 제거
Ojongseok Feb 8, 2026
90f7f1b
[feat] #91 카카오 로그인 실패 시 토스트메시지 노출 로직 추가
Ojongseok Feb 8, 2026
762c7a3
[fix] #91 KakaoAuthHelper.kt object로 변경
Ojongseok Feb 8, 2026
96bded3
[fix] #91 LoginScreen의 uiState 관찰 구문 제거
Ojongseok Feb 8, 2026
7ea2cd7
[fix] #91 온보딩 여부에 대한 키 및 함수명 수정
Ojongseok Feb 8, 2026
a872aa7
[fix] #91 불필요한 리소스 사용 제거
Ojongseok Feb 8, 2026
7a84fc3
[fix] #91 앱 로고 공통 컴포넌트로 분리
Ojongseok Feb 8, 2026
a0afa5f
[fix] #91 JWT토큰 저장 여부를 조회하는 함수명 변경
Ojongseok Feb 8, 2026
a026b95
[fix] #91 이용약관 동의 항목 터치 영역 수정
Ojongseok Feb 8, 2026
98a8c8e
[fix] #91 필수 이용약관 필터 로직 companion object로 이동
Ojongseok Feb 8, 2026
343f37f
[chore] #91 누락된 코드 추가
Ojongseok Feb 8, 2026
ee1c21e
[chore] #91 불필요한 import 제거
Ojongseok Feb 8, 2026
e4feb77
[fix] #91 Root 화면 전환 시 이전 Root 스택의 ViewModel 제거 로직 추가
Ojongseok Feb 8, 2026
ca5c575
[build] #91 detekt 룰 적용
Ojongseok Feb 8, 2026
19acb03
[feat] #91 토스트 메시지 NekiToast로 수정
Ojongseok Feb 8, 2026
e177740
[chore] #91 sharedViewModelStoreNavEntryDecorator 변수명 변경
Ojongseok Feb 8, 2026
5c8d6c9
[build] #91 불필요한 import 제거
Ojongseok Feb 8, 2026
6f1fdbf
[chore] #91 DataStoreKey.kt 제거 및 기존 AccessToken, RefreshToken 키 경로 이동
Ojongseok Feb 8, 2026
19ff2f2
[chore] #91 DataStoreRepository 제거
Ojongseok Feb 8, 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
30 changes: 30 additions & 0 deletions app/src/main/java/com/neki/android/app/AuthScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.neki.android.app

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.ui.NavDisplay

@Composable
fun AuthScreen(
entries: SnapshotStateList<NavEntry<NavKey>>,
onBack: () -> Unit,
) {
Scaffold(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding(),
) { innerPadding ->
NavDisplay(
modifier = Modifier.padding(innerPadding),
entries = entries,
onBack = onBack,
)
}
}
22 changes: 16 additions & 6 deletions app/src/main/java/com/neki/android/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import com.neki.android.core.navigation.result.ResultEventBus
import com.neki.android.core.navigation.root.RootNavKey
import com.neki.android.core.navigation.root.RootNavigationState
import com.neki.android.core.navigation.toEntries
import com.neki.android.feature.auth.impl.LoginRoute
import com.neki.android.core.navigation.auth.AuthNavigatorImpl
import com.neki.android.core.navigation.auth.toEntries
import com.neki.android.feature.auth.impl.navigation.authEntryProvider
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand All @@ -31,6 +33,9 @@ class MainActivity : ComponentActivity() {
@Inject
lateinit var rootNavigationState: RootNavigationState

@Inject
lateinit var authNavigator: AuthNavigatorImpl
Comment thread
Ojongseok marked this conversation as resolved.

@Inject
lateinit var navigator: NavigatorImpl

Expand All @@ -52,9 +57,14 @@ class MainActivity : ComponentActivity() {
NekiTheme {
CompositionLocalProvider(LocalResultEventBus provides resultBus) {
when (rootNavigationState.currentRootKey) {
RootNavKey.Login -> {
LoginRoute(
navigateToMain = { navigator.navigateRoot(RootNavKey.Main) },
RootNavKey.Auth -> {
AuthScreen(
entries = authNavigator.state.toEntries(
entryProvider = entryProvider {
authEntryProvider(authNavigator).invoke(this)
},
),
onBack = { authNavigator.goBack() },
)
}

Expand All @@ -70,7 +80,7 @@ class MainActivity : ComponentActivity() {
),
onTabSelected = { navigator.navigate(it) },
onBack = { navigator.goBack() },
navigateToLogin = { navigator.navigateRoot(RootNavKey.Login) },
navigateToLogin = { navigator.navigateRoot(RootNavKey.Auth) },
)
}
}
Expand All @@ -92,7 +102,7 @@ class MainActivity : ComponentActivity() {
Toast.LENGTH_SHORT,
).show()

navigator.navigateRoot(RootNavKey.Login)
navigator.navigateRoot(RootNavKey.Auth)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.neki.android.app.navigation.di

import com.neki.android.app.navigation.keys.START_AUTH_NAV_KEY
import com.neki.android.app.navigation.keys.START_NAV_KEY
import com.neki.android.app.navigation.keys.START_ROOT_NAV_KEY
import com.neki.android.app.navigation.keys.TOP_LEVEL_NAV_KEYS
import com.neki.android.core.navigation.NavigationState
import com.neki.android.core.navigation.auth.AuthNavigationState
import com.neki.android.core.navigation.root.RootNavigationState
import dagger.Module
import dagger.Provides
Expand All @@ -17,18 +19,26 @@ internal object NavigationModule {

@Provides
@ActivityRetainedScoped
fun providesNavigationState(): NavigationState {
return NavigationState(
startKey = START_NAV_KEY,
topLevelKeys = TOP_LEVEL_NAV_KEYS.toSet(),
fun providesRootNavigationState(): RootNavigationState {
return RootNavigationState(
startKey = START_ROOT_NAV_KEY,
)
}

@Provides
@ActivityRetainedScoped
fun providesRootNavigationState(): RootNavigationState {
return RootNavigationState(
startKey = START_ROOT_NAV_KEY,
fun providesAuthNavigationState(): AuthNavigationState {
return AuthNavigationState(
startKey = START_AUTH_NAV_KEY,
)
}

@Provides
@ActivityRetainedScoped
fun providesNavigationState(): NavigationState {
return NavigationState(
startKey = START_NAV_KEY,
topLevelKeys = TOP_LEVEL_NAV_KEYS.toSet(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.neki.android.app.navigation.keys
import com.neki.android.app.navigation.TopLevelNavItem
import com.neki.android.core.navigation.root.RootNavKey
import com.neki.android.feature.archive.api.ArchiveNavKey
import com.neki.android.feature.auth.api.AuthNavKey

internal val START_ROOT_NAV_KEY = RootNavKey.Login
internal val START_ROOT_NAV_KEY = RootNavKey.Auth
internal val START_AUTH_NAV_KEY = AuthNavKey.Splash
internal val START_NAV_KEY = ArchiveNavKey.Archive
internal val TOP_LEVEL_NAV_KEYS = TopLevelNavItem.entries.map { it.navKey }
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package com.neki.android.core.common.kakao
import android.content.Context
import com.kakao.sdk.user.UserApiClient

class KakaoAuthHelper(
private val context: Context,
) {
object KakaoAuthHelper {
fun login(
context: Context,
onSuccess: (String) -> Unit,
onFailure: (String) -> Unit,
) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.neki.android.core.dataapi.repository

import com.neki.android.core.model.Auth
import kotlinx.coroutines.flow.Flow

interface AuthRepository {
suspend fun loginWithKakao(idToken: String): Result<Auth>
suspend fun updateAccessToken(refreshToken: String): Result<Auth>
suspend fun withdrawAccount(): Result<Unit>

fun hasCompletedOnboarding(): Flow<Boolean>
suspend fun setCompletedOnboarding(value: Boolean)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface TokenRepository {
accessToken: String,
refreshToken: String,
)
fun isSavedTokens(): Flow<Boolean>
fun hasTokens(): Flow<Boolean>
fun getAccessToken(): Flow<String>
fun getRefreshToken(): Flow<String>
suspend fun clearTokens()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal object NetworkModule {
install(Auth) {
bearer {
loadTokens {
if (tokenRepository.isSavedTokens().first()) {
if (tokenRepository.hasTokens().first()) {
BearerTokens(
accessToken = tokenRepository.getAccessToken().first(),
refreshToken = tokenRepository.getRefreshToken().first(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.neki.android.core.data.repository.di

import com.neki.android.core.data.auth.AuthEventManagerImpl
import com.neki.android.core.data.repository.impl.AuthRepositoryImpl
import com.neki.android.core.data.repository.impl.DataStoreRepositoryImpl
import com.neki.android.core.data.repository.impl.MediaUploadRepositoryImpl
import com.neki.android.core.data.repository.impl.FolderRepositoryImpl
import com.neki.android.core.data.repository.impl.MapRepositoryImpl
Expand All @@ -13,7 +12,6 @@ import com.neki.android.core.data.repository.impl.UserRepositoryImpl
import com.neki.android.core.dataapi.auth.AuthEventManager
import com.neki.android.core.dataapi.repository.FolderRepository
import com.neki.android.core.dataapi.repository.AuthRepository
import com.neki.android.core.dataapi.repository.DataStoreRepository
import com.neki.android.core.dataapi.repository.MediaUploadRepository
import com.neki.android.core.dataapi.repository.MapRepository
import com.neki.android.core.dataapi.repository.PhotoRepository
Expand All @@ -30,12 +28,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal interface RepositoryModule {

@Binds
@Singleton
fun bindDataStoreRepositoryImpl(
dataStoreRepositoryImpl: DataStoreRepositoryImpl,
): DataStoreRepository

@Binds
@Singleton
fun bindAuthRepositoryImpl(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.neki.android.core.data.repository.impl

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import com.neki.android.core.data.local.di.AuthDataStore
import com.neki.android.core.data.remote.api.AuthService
import com.neki.android.core.data.remote.model.request.KakaoLoginRequest
import com.neki.android.core.data.remote.model.request.RefreshTokenRequest
import com.neki.android.core.data.util.runSuspendCatching
import com.neki.android.core.dataapi.repository.AuthRepository
import com.neki.android.core.model.Auth
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
@AuthDataStore private val dataStore: DataStore<Preferences>,
private val authService: AuthService,
) : AuthRepository {
override suspend fun loginWithKakao(idToken: String): Result<Auth> = runSuspendCatching {
Expand All @@ -30,4 +38,20 @@ class AuthRepositoryImpl @Inject constructor(
override suspend fun withdrawAccount(): Result<Unit> = runSuspendCatching {
authService.withdrawAccount()
}

override fun hasCompletedOnboarding(): Flow<Boolean> {
return dataStore.data.map { preferences ->
preferences[HAS_COMPLETED_ONBOARDING] ?: false
}
}

override suspend fun setCompletedOnboarding(value: Boolean) {
dataStore.edit { preferences ->
preferences[HAS_COMPLETED_ONBOARDING] = value
}
}

companion object {
private val HAS_COMPLETED_ONBOARDING = booleanPreferencesKey("has_completed_onboarding")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.neki.android.core.data.repository.impl
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.neki.android.core.common.crypto.CryptoManager
import com.neki.android.core.dataapi.datastore.DataStoreKey
import com.neki.android.core.dataapi.auth.AuthCacheManager
import com.neki.android.core.dataapi.repository.TokenRepository
import kotlinx.coroutines.flow.Flow
Expand All @@ -16,42 +16,48 @@ class TokenRepositoryImpl @Inject constructor(
@TokenDataStore private val dataStore: DataStore<Preferences>,
private val authCacheManager: AuthCacheManager,
) : TokenRepository {

companion object {
val ACCESS_TOKEN = stringPreferencesKey("access_token")
val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
}

override suspend fun saveTokens(
accessToken: String,
refreshToken: String,
) {
dataStore.edit { preferences ->
preferences[DataStoreKey.ACCESS_TOKEN] = CryptoManager.encrypt(accessToken)
preferences[DataStoreKey.REFRESH_TOKEN] = CryptoManager.encrypt(refreshToken)
preferences[ACCESS_TOKEN] = CryptoManager.encrypt(accessToken)
preferences[REFRESH_TOKEN] = CryptoManager.encrypt(refreshToken)
}
authCacheManager.invalidateTokenCache()
}

override fun isSavedTokens(): Flow<Boolean> {
override fun hasTokens(): Flow<Boolean> {
return dataStore.data.map { preferences ->
val accessToken = preferences[DataStoreKey.ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
val refreshToken = preferences[DataStoreKey.REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }
val accessToken = preferences[ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
val refreshToken = preferences[REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }

!accessToken.isNullOrBlank() && !refreshToken.isNullOrBlank()
}
}

override fun getAccessToken(): Flow<String> {
return dataStore.data.map { preferences ->
preferences[DataStoreKey.ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
preferences[ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
}
}

override fun getRefreshToken(): Flow<String> {
return dataStore.data.map { preferences ->
preferences[DataStoreKey.REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
preferences[REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
}
}

override suspend fun clearTokens() {
dataStore.edit { preferences ->
preferences.remove(DataStoreKey.ACCESS_TOKEN)
preferences.remove(DataStoreKey.REFRESH_TOKEN)
preferences.remove(ACCESS_TOKEN)
preferences.remove(REFRESH_TOKEN)
}
authCacheManager.invalidateTokenCache()
}
Expand Down
Loading