Skip to content
Open
Show file tree
Hide file tree
Changes from 18 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
*.iml
*.jks
*.keystore
.gradle
/local.properties
.idea/
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ android {
namespace = "com.example.linku_android"
compileSdk = 35


defaultConfig {
applicationId = "com.example.linku_android"
minSdk = 26
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/java/com/example/linku_android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Text
import androidx.core.view.WindowCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.rememberNavController
import com.example.login.auth.AnimatedLoginScreen
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
Expand All @@ -20,6 +16,7 @@ class MainActivity : ComponentActivity() {


intent?.data?.let { Log.d("DEEPLINK", "onCreate uri = $it") }

WindowCompat.setDecorFitsSystemWindows(window, false)
//enableEdgeToEdge()
setContent {
Expand All @@ -29,5 +26,7 @@ class MainActivity : ComponentActivity() {
}




}
}
107 changes: 56 additions & 51 deletions app/src/main/java/com/example/linku_android/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,21 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import com.example.home.HomeApp
import com.example.curation.ui.CurationDetailScreen
import com.example.curation.ui.CurationScreen
import com.example.login.auth.AnimatedLoginScreen
import com.example.login.auth.EmailVerificationScreen
import com.example.login.auth.ServiceTermsScreen
import com.example.login.auth.PrivacyTermsScreenFixed
import com.example.login.auth.MarketingTermsScreenComposable
import com.example.login.auth.SignUpPasswordScreen
import com.example.login.auth.EmailLoginScreen
import com.example.login.auth.InterestContentScreen
import com.example.login.auth.InterestPurposeScreen
import com.example.login.auth.SignUpGenderScreen
import com.example.login.auth.SignUpNicknameScreen
import com.example.login.auth.SignUpJobScreen
import com.example.login.auth.WelcomeScreen
import com.example.login.auth.ResetPasswordScreen
import com.example.login.auth.SignUpViewModel
import com.example.login.ui.animation.AnimatedLoginScreen
import com.example.login.ui.screen.EmailVerificationScreen
import com.example.login.ui.terms.ServiceTermsScreen
import com.example.login.ui.terms.PrivacyTermsScreenFixed
import com.example.login.ui.terms.MarketingTermsScreenComposable
import com.example.login.ui.screen.SignUpPasswordScreen
import com.example.login.ui.screen.EmailLoginScreen
import com.example.login.ui.screen.InterestContentScreen
import com.example.login.ui.screen.InterestPurposeScreen
import com.example.login.ui.screen.SignUpGenderScreen
import com.example.login.ui.screen.SignUpNicknameScreen
import com.example.login.ui.screen.SignUpJobScreen
import com.example.login.ui.screen.WelcomeScreen
import com.example.login.ui.screen.ResetPasswordScreen
import com.example.login.viewmodel.SignUpViewModel
import java.io.File
import java.io.FileOutputStream

Expand All @@ -82,7 +82,7 @@ import com.example.file.ui.theme.DefaultFont
import com.example.file.ui.theme.Gray600
import com.example.file.viewmodel.folder.state.FolderStateViewModel
import com.example.linku_android.deeplink.DeepLinkHandlerViewModel
import com.example.login.auth.LoginViewModel
import com.example.login.viewmodel.LoginViewModel

import dagger.hilt.android.EntryPointAccessors
import androidx.core.net.toUri
Expand Down Expand Up @@ -260,56 +260,39 @@ fun MainApp(
) {

/* ① Login composable */
composable(NavigationRoute.Login.route) { entry ->
val parentEntry = entry
composable(NavigationRoute.Login.route) { parentEntry ->
val signUpVm: SignUpViewModel = hiltViewModel(parentEntry)

val showTermsSheet by parentEntry.savedStateHandle
.getStateFlow("show_terms_sheet", false)
.collectAsStateWithLifecycle()

BackHandler(enabled = showTermsSheet) {
parentEntry.savedStateHandle["show_terms_sheet"] = false
}

// 이메일 인증에서 백버튼으로 갔을 때, 약관 페이지 나오는게 맞는지.
val skipAnimation =
parentEntry.savedStateHandle
.get<Boolean>("skip_login_animation") == true

// 이메일 인증에서 돌아오는지 확인
var cameFromEmail by remember { mutableStateOf(false) }

LaunchedEffect(navigator.currentBackStackEntry) {
if (parentEntry.savedStateHandle.get<Boolean>("from_email_verification") == true) {
cameFromEmail = true
parentEntry.savedStateHandle["show_terms_sheet"] = true

kotlinx.coroutines.delay(120)

cameFromEmail = false
parentEntry.savedStateHandle["from_email_verification"] = false
// 읽은 직후 초기화
LaunchedEffect(skipAnimation) {
if (skipAnimation) {
parentEntry.savedStateHandle["skip_login_animation"] = false
}
}

// 약간의 지연 + 재렌더링 위해 빈 박스 만듬.
if (cameFromEmail) {
Box(Modifier.fillMaxSize()) {}
return@composable
}


AnimatedLoginScreen(
navigator = navigator,
skipAnimation = skipAnimation,
onSignUpClick = {
parentEntry.savedStateHandle["show_terms_sheet"] = true
}
)

}

/* ② Service Terms */
composable("terms/service") { entry ->
val parentEntry = remember(entry) { navigator.getBackStackEntry("auth_graph") }
val vm: SignUpViewModel = hiltViewModel(parentEntry)

//시스템 백버튼 처리
BackHandler {
parentEntry.savedStateHandle["show_terms_sheet"] = true
navigator.popBackStack()
}

ServiceTermsScreen(
onBackClicked = {
parentEntry.savedStateHandle["show_terms_sheet"] = true
Expand Down Expand Up @@ -368,6 +351,15 @@ fun MainApp(


val vm: SignUpViewModel = hiltViewModel(parentEntry)
//백버튼으로 온 경우 애니메이션 적용X
BackHandler {
// 로그인 화면(AnimatedLoginScreen)에 애니메이션 스킵 플래그 전달함.
parentEntry.savedStateHandle["skip_login_animation"] = true
parentEntry.savedStateHandle["from_email_verification"] = true

navigator.popBackStack()
}

EmailVerificationScreen(
navigator = navigator,
parentEntry = parentEntry, // ⬅ 추가
Expand Down Expand Up @@ -433,14 +425,19 @@ fun MainApp(

composable("email_login") {

val parentEntry = remember {
val parentEntry = remember(navigator.currentBackStackEntry) {
navigator.getBackStackEntry("auth_graph")
}

val showTermsSheet by parentEntry.savedStateHandle
.getStateFlow("show_terms_sheet", false)
.collectAsStateWithLifecycle()

//약관 바텀시트 떠 있을 때 백버튼 = 시트 닫기
BackHandler(enabled = showTermsSheet) {
parentEntry.savedStateHandle["show_terms_sheet"] = false
}

LaunchedEffect(Unit) { showNavBar = false }

// 로그인 상태 관찰
Expand Down Expand Up @@ -821,12 +818,20 @@ fun MainApp(

// ❺ 실제 로그인 UI(AnimatedLoginScreen 등) 렌더링
// AnimatedLoginScreen(navigator = navigator)
AnimatedLoginScreen(navigator = navigator, onSignUpClick = {})
val skipAnimation =
backStackEntry.savedStateHandle
.get<Boolean>("skip_login_animation") == true

AnimatedLoginScreen(
navigator = navigator,
skipAnimation = skipAnimation,
onSignUpClick = {}
)
}



// TODO: 로그인 되어 있지 않은 상황 처리
// TODO: 로그인 되어 있지 않은 상황 처리 ?이게 뭐람
// 링크 공유 앱링크
composable(
route = "open?action={action}&folderId={folderId}",
Expand Down
40 changes: 8 additions & 32 deletions app/src/main/java/com/example/linku_android/Splash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import com.example.design.util.PixelScaler
import android.app.Activity
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import android.view.WindowInsets
import com.example.design.util.DesignSystemBars



Expand All @@ -50,31 +44,13 @@ interface SplashDeps {
@Composable
fun Splash(onResult: (Boolean) -> Unit) {

val view = LocalView.current
val isPreview = LocalInspectionMode.current

if (!isPreview) {
val activity = view.context as Activity
val window = activity.window

SideEffect {
// edge-to-edge
WindowCompat.setDecorFitsSystemWindows(window, false)

// 상태바 + 네비게이션바 완전 숨김
WindowInsetsControllerCompat(window, view).apply {
hide(
WindowInsets.Type.statusBars() or
WindowInsets.Type.navigationBars()
)

// 스와이프로 잠깐 나타났다가 다시 숨김
systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}

//바텀바 숨김
DesignSystemBars(
statusBarColor = Color.Transparent,
navigationBarColor = Color.Transparent,
darkIcons = false,
immersive = true
)
val rotationAnim = remember { Animatable(0f) }
var isGlowPhase by remember { mutableStateOf(false) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

data class UserInfoDTO(
// @Json(name = "nickname")
// val nickname: String,

// TODO: 통합
@Json(name = "nickname")
val nickname: String? = null,
// Done 통합 : 01.13 완료 했습니다. (username 제거)
@Json(name = "nickName")
val nickName: String? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,25 +151,19 @@ class UserRepositoryImpl @Inject constructor(

// 마이페이지 조회
override suspend fun getUserInfo(userId: Long): UserInfo {
// val response = userApi.withAuth(authPreference) { getUserInfo(userId) }
val response = userApi.getUserInfo(userId)

val dto: UserInfoDTO = response.result
val dto = response.result
?: throw IllegalStateException("마이페이지 조회 실패: ${response.message}")

// DTO -> 도메인 매핑을 여기서 바로 처리
val nick = dto.nickname ?: dto.nickName ?: "" // ← Fallback 추가

// 서버에서 받은 enum 코드 → 화면 한글 라벨로 변환
// 서버 enum → 한글
val displayPurposes = dto.purposes.map { reversePurposeMap[it] ?: it }
val displayInterests = dto.interests.map { reverseInterestMap[it] ?: it }

// DTO -> 도메인 매핑을 여기서 바로 처리
return UserInfo(
// nickname = dto.nickname,
nickname = nick,
nickname = dto.nickName.orEmpty(),
email = dto.email,
gender = dto.gender.value, // "MALE" | "FEMALE"
gender = dto.gender.value,
jobId = dto.job.id,
jobName = dto.job.name,
myLinku = dto.myLinku,
Expand Down Expand Up @@ -224,12 +218,11 @@ class UserRepositoryImpl @Inject constructor(
// 닉네임 전용 메서드로 분리
override suspend fun getNickname(userId: Long): String? {
return try {
val res = userApi.getUserInfo(userId) // BaseResponse<UserInfoDTO>
// 서버 DTO 필드명 대응 (nickname 혹은 nickName)
val nick = res.result?.nickname ?: res.result?.nickName
val res = userApi.getUserInfo(userId)
val nick = res.result?.nickName
Log.d("UserRepository", "닉네임=$nick")
nick?.takeIf { it.isNotBlank() }
} catch (e: retrofit2.HttpException) {
} catch (e: HttpException) {
if (e.code() == 500) null else throw e
} catch (e: Exception) {
Log.e("UserRepository", "닉네임 가져오기 실패", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.example.design.theme.color.ThemeColorScheme
import com.example.design.theme.font.Paperlogy
import com.example.design.theme.font.ThemeFontScheme
import com.example.design.theme.font.getTypography
import com.example.design.util.LocalFigmaDimens
import com.example.design.util.rememberFigmaDimens

val LocalColorTheme = compositionLocalOf<ThemeColorScheme> { Basic }
val LocalFontTheme = compositionLocalOf<ThemeFontScheme> { Paperlogy }
Expand All @@ -22,9 +24,13 @@ fun ThemeProvider(
) {
val currentTypography = MaterialTheme.typography

// scaler 함수 생성
val figmaScale = rememberFigmaDimens()

CompositionLocalProvider(
LocalColorTheme provides colorScheme,
LocalFontTheme provides fontScheme,
LocalFigmaDimens provides figmaScale
) {
MaterialTheme(
typography = remember(key1 = fontScheme, key2 = currentTypography) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data object Basic: ThemeColorScheme(
maincolor= Brush.horizontalGradient(
listOf(
Color(0xFF2C6FFF),
Color(0xFFCB59EB)
Color(0xFFC800FF) //수정함.
)
),
blue = ColorMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ sealed class ThemeColorScheme(
800 to Color(0xFF43454B),
),

// 비활성화용 그라데이션 브러시 추가 -로그인, 회원가입용
val inactiveColor: Brush = Brush.horizontalGradient(
listOf(Color(0xFFD4E1FF), Color(0xFFF2CCFF))
),

val black: Color = Color(0xFF000208),
val white: Color = Color(0xFFFFFFFF),
val positive: Color = Color(0xFF35DF79),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fun SearchBarTopSheet(
* - 350ms 디바운스
* - 동일 값 중복 호출 방지
*/
LaunchedEffect(text) {
LaunchedEffect(Unit) {
snapshotFlow { text }
.map { it.trim() }
.filter { it.length >= 2 }
Copy link
Contributor

Choose a reason for hiding this comment

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

검색했을 때, 한 글자만 검색한 사용자 입장에서 왜 검색이 안되지? 싶을 수 있어서, 디자이너와 상의 후 관련 ui 추가하는 건 어떠실까요?

Copy link
Contributor

Choose a reason for hiding this comment

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

검색했을 때, 한 글자만 검색한 사용자 입장에서 왜 검색이 안되지? 싶을 수 있어서, 디자이너와 상의 후 관련 ui 추가하는 건 어떠실까요?

윤다인과 이다현의 TODO. 한글자일 때 비활성화+상태메시지 ui

Expand Down
Loading