diff --git a/app/src/main/java/com/example/it_da/navigation/AppNavigation.kt b/app/src/main/java/com/example/it_da/navigation/AppNavigation.kt index bbb9777..f01b30a 100644 --- a/app/src/main/java/com/example/it_da/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/it_da/navigation/AppNavigation.kt @@ -6,6 +6,12 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.it_da.ui.screen.home.HomeRoute import com.example.it_da.ui.screen.login.LoginRoute +import com.example.it_da.ui.screen.profile.NotificationSettingsRoute +import com.example.it_da.ui.screen.profile.PersonalInfoRoute +import com.example.it_da.ui.screen.profile.ProfileRoute +import com.example.it_da.ui.screen.profile.ProjectStatusRoute +import com.example.it_da.ui.screen.profile.SelfIntroductionRoute +import com.example.it_da.ui.screen.profile.VersionInfoRoute import com.example.it_da.ui.screen.signup.route.SignUpAccountRoute import com.example.it_da.ui.screen.signup.route.SignUpAdditionalInfoRoute @@ -49,7 +55,75 @@ fun AppNavigation() { } composable(AppRoute.Home.path) { - HomeRoute() + HomeRoute( + onProfileTabClick = { + navController.navigate(AppRoute.Profile.path) + } + ) + } + + composable(AppRoute.Profile.path) { + ProfileRoute( + onHomeTabClick = { + navController.navigate(AppRoute.Home.path) + }, + onNotificationSettingsClick = { + navController.navigate(AppRoute.NotificationSettings.path) + }, + onProjectStatusClick = { + navController.navigate(AppRoute.ProjectStatus.path) + }, + onSelfIntroductionClick = { + navController.navigate(AppRoute.SelfIntroduction.path) + }, + onPersonalInfoClick = { + navController.navigate(AppRoute.PersonalInfo.path) + } + ) + } + + composable(AppRoute.PersonalInfo.path) { + PersonalInfoRoute( + onBackClick = { navController.popBackStack() } + ) + } + + composable(AppRoute.SelfIntroduction.path) { + SelfIntroductionRoute( + onBackClick = { navController.popBackStack() } + ) + } + + composable(AppRoute.ProjectStatus.path) { + ProjectStatusRoute( + onBackClick = { + navController.popBackStack() + } + ) + } + + composable(AppRoute.NotificationSettings.path) { + NotificationSettingsRoute( + onBackClick = { + navController.popBackStack() + }, + onVersionInfoClick = { + navController.navigate(AppRoute.VersionInfo.path) + }, + onSignOutClick = { + navController.navigate(AppRoute.Login.path) { + popUpTo(navController.graph.id) { inclusive = true } + } + } + ) + } + + composable(AppRoute.VersionInfo.path) { + VersionInfoRoute( + onBackClick = { + navController.popBackStack() + } + ) } } } diff --git a/app/src/main/java/com/example/it_da/navigation/AppRoute.kt b/app/src/main/java/com/example/it_da/navigation/AppRoute.kt index cd2d5c4..10838ae 100644 --- a/app/src/main/java/com/example/it_da/navigation/AppRoute.kt +++ b/app/src/main/java/com/example/it_da/navigation/AppRoute.kt @@ -6,4 +6,10 @@ sealed class AppRoute(val path: String) { data object SignUpAccount : AppRoute("sign_up_account") data object SignUpAdditionalInfo : AppRoute("sign_up_additional_info") data object Home : AppRoute("home") + data object Profile : AppRoute("profile") + data object PersonalInfo : AppRoute("personal_info") + data object SelfIntroduction : AppRoute("self_introduction") + data object ProjectStatus : AppRoute("project_status") + data object NotificationSettings : AppRoute("notification_settings") + data object VersionInfo : AppRoute("version_info") } diff --git a/app/src/main/java/com/example/it_da/ui/screen/home/HomeRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/home/HomeRoute.kt index e7e704f..9bc1092 100644 --- a/app/src/main/java/com/example/it_da/ui/screen/home/HomeRoute.kt +++ b/app/src/main/java/com/example/it_da/ui/screen/home/HomeRoute.kt @@ -10,6 +10,7 @@ import com.example.it_da.ui.screen.home.viewmodel.HomeViewModelFactory // Connects HomeViewModel state to the home screen and leaves future navigation targets as callbacks. @Composable fun HomeRoute( + onProfileTabClick: () -> Unit = {}, viewModel: HomeViewModel = viewModel(factory = HomeViewModelFactory()) ) { val uiState by viewModel.uiState.collectAsState() @@ -27,6 +28,6 @@ fun HomeRoute( onExploreTabClick = {}, onCreateProjectClick = {}, onNotificationTabClick = {}, - onProfileTabClick = {} + onProfileTabClick = onProfileTabClick ) } diff --git a/app/src/main/java/com/example/it_da/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/home/HomeScreen.kt index ff9a4e6..1c3cd18 100644 --- a/app/src/main/java/com/example/it_da/ui/screen/home/HomeScreen.kt +++ b/app/src/main/java/com/example/it_da/ui/screen/home/HomeScreen.kt @@ -19,22 +19,21 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.it_da.R -import com.example.it_da.ui.screen.home.component.HomeBottomNavigationBar import com.example.it_da.ui.component.section.HomeNotificationSection import com.example.it_da.ui.component.section.HomeProfileSummarySection import com.example.it_da.ui.component.section.ParticipatingProjectSection import com.example.it_da.ui.component.section.RecommendedProjectSection -import com.example.it_da.ui.screen.signup.component.SignUpPrimaryButton -import com.example.it_da.ui.screen.signup.component.SignUpTopBar +import com.example.it_da.ui.screen.home.component.HomeBottomNavigationBar import com.example.it_da.ui.screen.home.state.HomeNotificationUiModel import com.example.it_da.ui.screen.home.state.HomeProjectCountUiModel import com.example.it_da.ui.screen.home.state.HomeUiState import com.example.it_da.ui.screen.home.state.ParticipatingProjectUiModel import com.example.it_da.ui.screen.home.state.RecommendedProjectUiModel -import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray +import com.example.it_da.ui.screen.signup.component.SignUpPrimaryButton +import com.example.it_da.ui.screen.signup.component.SignUpTopBar import com.example.it_da.ui.theme.ITDATheme +import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray -// Assembles the home screen from state-driven sections and button callbacks. @Composable fun HomeScreen( uiState: HomeUiState, @@ -87,7 +86,6 @@ fun HomeScreen( } } -// Lays out the scrollable home content while the bottom navigation remains fixed. @Composable private fun HomeContent( uiState: HomeUiState, @@ -173,12 +171,11 @@ private fun HomeScreenPreview() { } } -// Supplies preview-only values so the screen can be checked without the ViewModel. private fun previewHomeUiState(): HomeUiState { return HomeUiState( - userName = "000", + userName = "메타몽", profileImageResId = R.drawable.home_profile_placeholder, - greetingDescription = "상상은 여기서 현실이 됩니다.\n당신의 프로젝트와 팀을 찾아보세요", + greetingDescription = "오늘도 멋진 하루예요.\n딱 맞는 프로젝트를 찾아보세요!", projectCount = HomeProjectCountUiModel( applyingCount = 3, participatingCount = 1, @@ -187,38 +184,39 @@ private fun previewHomeUiState(): HomeUiState { recommendedProjects = listOf( RecommendedProjectUiModel( id = "recommended-ai-planner", - title = "AI 기반 학습 플래너 [0부0부]", + title = "AI 기반 학습 플래너", recruitingSummary = "백엔드 개발자 1명 모집", statusText = "모집 중", techStacks = listOf("Back-end"), - participantSummary = "IoT과ㆍ2명, SW과 1명 참여" + participantSummary = "IoT공학 2명, SW공학 1명 참여" ), RecommendedProjectUiModel( id = "recommended-pokemon", - title = "닮은 포켓몬 검사 [포켓몬백]", - recruitingSummary = "iOS 개발자ㆍ1명ㆍ디자이너 1명 모집", + title = "포켓몬 도감 검색 앱", + recruitingSummary = "iOS 개발 1명, 디자이너 1명 모집", statusText = "마감 임박", techStacks = listOf("iOS", "Design"), - participantSummary = "IoT과ㆍ2명, SW과 1명 참여" + participantSummary = "IoT공학 2명, SW공학 1명 참여" ) ), participatingProjects = listOf( ParticipatingProjectUiModel( id = "participating-dalbal", - title = "사랑을 이어주는 앱 [달발]", - myRole = "내 역할 : iOS 개발", + title = "달려서 케어해주는 앱", + myRole = "내 역할: iOS 개발", statusText = "진행 중", - teamSummary = "팀원 4명ㆍ마감 2026-05-31" + teamSummary = "총 4명, 마감 2026-05-31" ) ), notifications = listOf( HomeNotificationUiModel( id = "notification-message", imageResId = R.drawable.home_notification_mailbox, - imageDescription = "새 메시지 알림", - message = "지원한 프로젝트에서 새 메시지가 있습니다", - elapsedTime = "2분전" + imageDescription = "메시지 알림", + message = "진행 중인 프로젝트에서 새 메시지가 도착했습니다.", + elapsedTime = "2분 전" ) ) ) } + diff --git a/app/src/main/java/com/example/it_da/ui/screen/home/component/HomeBottomNavigationBar.kt b/app/src/main/java/com/example/it_da/ui/screen/home/component/HomeBottomNavigationBar.kt index 08538cf..28e4208 100644 --- a/app/src/main/java/com/example/it_da/ui/screen/home/component/HomeBottomNavigationBar.kt +++ b/app/src/main/java/com/example/it_da/ui/screen/home/component/HomeBottomNavigationBar.kt @@ -35,7 +35,6 @@ private val HomeBottomNavigationBarHeight = 52.5.dp private val HomeBottomNavigationContainerHeight = 62.dp private val HomeBottomNavigationItemHeight = 48.dp -// Shows the fixed bottom navigation bar and exposes each tab as a callback. @Composable fun HomeBottomNavigationBar( onHomeClick: () -> Unit, @@ -66,7 +65,7 @@ fun HomeBottomNavigationBar( HomeBottomNavigationItem( iconResId = R.drawable.bottom_bar_home, label = "홈", - contentDescription = "홈", + contentDescription = "홈 탭", iconSize = 25.dp, onClick = onHomeClick ) @@ -74,7 +73,7 @@ fun HomeBottomNavigationBar( HomeBottomNavigationItem( iconResId = R.drawable.bottom_bar_research, label = "탐색", - contentDescription = "탐색", + contentDescription = "탐색 탭", iconSize = 25.dp, onClick = onExploreClick ) @@ -88,15 +87,15 @@ fun HomeBottomNavigationBar( HomeBottomNavigationItem( iconResId = R.drawable.bottom_bar_bell, label = "알림", - contentDescription = "알림", + contentDescription = "알림 탭", iconSize = 25.dp, onClick = onNotificationClick ) HomeBottomNavigationItem( iconResId = R.drawable.bottom_bar_profile, - label = "프로필", - contentDescription = "프로필", + label = "My", + contentDescription = "내 프로필 탭", iconSize = 25.dp, onClick = onProfileClick ) @@ -110,7 +109,6 @@ fun HomeBottomNavigationBar( } } -// Shows a normal labeled bottom navigation item. @Composable private fun HomeBottomNavigationItem( @DrawableRes iconResId: Int, @@ -148,7 +146,6 @@ private fun HomeBottomNavigationItem( } } -// Shows the center add-project action as the prominent middle bottom button. @Composable private fun HomeCenterNavigationButton( onClick: () -> Unit, @@ -157,7 +154,7 @@ private fun HomeCenterNavigationButton( Box( modifier = modifier .size(45.dp) - .clip(androidx.compose.foundation.shape.RoundedCornerShape(12.dp)) + .clip(RoundedCornerShape(12.dp)) .clickable(onClick = onClick), contentAlignment = Alignment.Center ) { @@ -168,3 +165,4 @@ private fun HomeCenterNavigationButton( ) } } + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/MyProfileScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/MyProfileScreen.kt new file mode 100644 index 0000000..be7aad3 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/MyProfileScreen.kt @@ -0,0 +1,348 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +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.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.component.ItdaOutlinedBadge +import com.example.it_da.ui.screen.home.component.HomeBottomNavigationBar +import com.example.it_da.ui.screen.signup.component.SignUpTopBar +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val ProfileOuterHorizontalPadding = 24.dp +private val ProfileOuterTopPadding = 22.dp +private val ProfileOuterBottomPadding = 84.dp +private val ProfileSectionLargeSpacing = 24.dp +private val ProfileSectionMediumSpacing = 20.dp +private val ProfileTitleToContentSpacing = 10.dp +private val ProfileHeaderToButtonSpacing = 14.dp +private val ProfileButtonToFirstSectionSpacing = 22.dp +private val ProfileSummaryTopSpacing = 12.dp +private val ProfileActionRowTopSpacing = 12.dp +private val ProfileChipSpacing = 6.dp +private val ProfileCardRowSpacing = 12.dp +private val ProfileHeaderAvatarTextSpacing = 20.dp +private val ProfileHeaderTextSpacing = 2.dp + +@Composable +fun MyProfileScreen( + onHomeTabClick: () -> Unit, + onExploreTabClick: () -> Unit, + onCreateProjectClick: () -> Unit, + onNotificationTabClick: () -> Unit, + onProfileTabClick: () -> Unit, + onNotificationSettingsClick: () -> Unit, + onProjectStatusClick: () -> Unit, + onSelfIntroductionClick: () -> Unit, + onPersonalInfoClick: () -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + ) { + Column(modifier = Modifier.fillMaxSize()) { + SignUpTopBar(title = "Project") + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = ProfileOuterHorizontalPadding) + .padding(top = ProfileOuterTopPadding, bottom = ProfileOuterBottomPadding), + verticalArrangement = Arrangement.spacedBy(ProfileSectionMediumSpacing) + ) { + MyProfileHeader() + + ActionButton( + text = "개인 정보 입력", + onClick = onPersonalInfoClick + ) + + SectionBlock( + title = "기술 스택", + titleToContentSpacing = ProfileHeaderToButtonSpacing + ) { + Row(horizontalArrangement = Arrangement.spacedBy(ProfileChipSpacing)) { + ItdaOutlinedBadge(text = "Back-end") + ItdaOutlinedBadge(text = "Java") + ItdaOutlinedBadge(text = "JavaScript") + ItdaOutlinedBadge(text = "Design") + } + } + + SectionBlock(title = "경력") { + EmptyBox(height = 86.dp) + } + + SectionBlock(title = "자기 소개") { + EmptyBox( + height = 90.dp, + text = "자기소개 작성하기", + onClick = onSelfIntroductionClick + ) + } + + Column( + verticalArrangement = Arrangement.spacedBy(ProfileSummaryTopSpacing) + ) { + SectionTitle(text = "활동 요약") + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(ProfileCardRowSpacing) + ) { + SummaryCard(value = "2", label = "참여 프로젝트", modifier = Modifier.weight(1f)) + SummaryCard(value = "3", label = "지원 내역", modifier = Modifier.weight(1f)) + SummaryCard(value = "1", label = "받은 제안", modifier = Modifier.weight(1f)) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(ProfileCardRowSpacing) + ) { + ActionButton( + text = "프로젝트 전체 보기", + modifier = Modifier.weight(1.35f), + onClick = onProjectStatusClick + ) + ActionButton( + text = "설정", + modifier = Modifier.weight(1f), + onClick = onNotificationSettingsClick + ) + } + } + } + } + + HomeBottomNavigationBar( + onHomeClick = onHomeTabClick, + onExploreClick = onExploreTabClick, + onCreateProjectClick = onCreateProjectClick, + onNotificationClick = onNotificationTabClick, + onProfileClick = onProfileTabClick, + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + ) + } +} + +@Composable +private fun SectionBlock( + title: String, + titleToContentSpacing: Dp = ProfileTitleToContentSpacing, + content: @Composable () -> Unit +) { + Column( + verticalArrangement = Arrangement.spacedBy(titleToContentSpacing) + ) { + SectionTitle(text = title) + content() + } +} + +@Composable +private fun MyProfileHeader() { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(ProfileHeaderAvatarTextSpacing) + ) { + Surface( + modifier = Modifier.size(92.dp), + shape = CircleShape, + color = Color(0xFFE3E3E3) + ) { + Box(contentAlignment = Alignment.Center) { + Surface( + modifier = Modifier.size(38.dp), + shape = CircleShape, + color = Color(0xFF8F8F8F) + ) {} + } + } + + Column(verticalArrangement = Arrangement.spacedBy(ProfileHeaderTextSpacing)) { + Text( + text = "메타몽", + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 35.sp, + lineHeight = 40.sp, + color = MaterialTheme.colorScheme.onBackground + ) + Text( + text = "SW개발과 · 1학년", + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 23.sp, + lineHeight = 26.sp, + color = MaterialTheme.colorScheme.onBackground + ) + Text( + text = "가입일 2025년 1월", + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 23.sp, + lineHeight = 26.sp, + color = MaterialTheme.colorScheme.onBackground + ) + } + } +} + +@Composable +private fun SectionTitle(text: String) { + Text( + text = text, + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 27.sp, + lineHeight = 31.sp, + color = MaterialTheme.colorScheme.onBackground + ) +} + +@Composable +private fun EmptyBox( + height: Dp, + text: String? = null, + onClick: (() -> Unit)? = null +) { + Surface( + modifier = Modifier + .fillMaxWidth() + .height(height) + .let { base -> if (onClick != null) base.clickable(onClick = onClick) else base }, + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) { + if (text != null) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = text, + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = ItdaSecondaryTextColor + ) + } + } + } +} + +@Composable +private fun SummaryCard( + value: String, + label: String, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier.height(72.dp), + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = value, + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 17.sp, + lineHeight = 20.sp, + color = MaterialTheme.colorScheme.onBackground + ) + Text( + text = label, + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 11.sp, + lineHeight = 13.sp, + color = ItdaSecondaryTextColor + ) + } + } +} + +@Composable +private fun ActionButton( + text: String, + modifier: Modifier = Modifier, + onClick: () -> Unit = {} +) { + Surface( + modifier = modifier + .height(50.dp) + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = text, + fontFamily = DotSans, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 16.sp, + color = ItdaSecondaryTextColor + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun MyProfileScreenPreview() { + ITDATheme { + MyProfileScreen( + onHomeTabClick = {}, + onExploreTabClick = {}, + onCreateProjectClick = {}, + onNotificationTabClick = {}, + onProfileTabClick = {}, + onNotificationSettingsClick = {}, + onProjectStatusClick = {}, + onSelfIntroductionClick = {}, + onPersonalInfoClick = {} + ) + } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsRoute.kt new file mode 100644 index 0000000..2e3c202 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsRoute.kt @@ -0,0 +1,16 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +@Composable +fun NotificationSettingsRoute( + onBackClick: () -> Unit, + onVersionInfoClick: () -> Unit, + onSignOutClick: () -> Unit +) { + NotificationSettingsScreen( + onBackClick = onBackClick, + onVersionInfoClick = onVersionInfoClick, + onSignOutClick = onSignOutClick + ) +} diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsScreen.kt new file mode 100644 index 0000000..5f74fc7 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/NotificationSettingsScreen.kt @@ -0,0 +1,252 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +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.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val SettingsHorizontalPadding = 24.dp +private val SettingsTopSpacing = 22.dp +private val SettingsSectionSpacing = 10.dp +private val SettingsMenuSpacing = 4.dp +private val SettingsItemInnerVertical = 14.dp +private val SettingsItemInnerHorizontal = 14.dp +private val SettingsDescriptionSpacing = 4.dp + +@Composable +fun NotificationSettingsScreen( + onBackClick: () -> Unit, + onVersionInfoClick: () -> Unit, + onSignOutClick: () -> Unit, + modifier: Modifier = Modifier +) { + var projectNoticeEnabled by rememberSaveable { mutableStateOf(true) } + var applicationNoticeEnabled by rememberSaveable { mutableStateOf(true) } + var messageNoticeEnabled by rememberSaveable { mutableStateOf(true) } + var marketingNoticeEnabled by rememberSaveable { mutableStateOf(false) } + var showSignOutDialog by rememberSaveable { mutableStateOf(false) } + + if (showSignOutDialog) { + AlertDialog( + onDismissRequest = { showSignOutDialog = false }, + title = { + Text("로그아웃", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + }, + text = { + Text("정말 로그아웃하시겠습니까?", fontFamily = DotSans, fontSize = 14.sp) + }, + confirmButton = { + Text( + text = "로그아웃", + modifier = Modifier.clickable { + showSignOutDialog = false + onSignOutClick() + }, + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + color = ItdaHomeExploreButtonGray + ) + }, + dismissButton = { + Text( + text = "취소", + modifier = Modifier.clickable { showSignOutDialog = false }, + fontFamily = DotSans, + fontSize = 14.sp, + color = ItdaSecondaryTextColor + ) + }, + containerColor = ItdaWhite + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .padding(horizontal = SettingsHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(SettingsSectionSpacing) + ) { + NotificationSettingsTopBar(onBackClick = onBackClick) + + Column(verticalArrangement = Arrangement.spacedBy(SettingsSectionSpacing)) { + SettingItem( + title = "프로젝트 알림", + description = "프로젝트 참여/변경 관련 알림을 받습니다.", + enabled = projectNoticeEnabled, + onCheckedChange = { projectNoticeEnabled = it } + ) + SettingItem( + title = "지원 내역 알림", + description = "지원 결과와 상태 변경 알림을 받습니다.", + enabled = applicationNoticeEnabled, + onCheckedChange = { applicationNoticeEnabled = it } + ) + SettingItem( + title = "메시지 알림", + description = "새 메시지 도착 시 알림을 받습니다.", + enabled = messageNoticeEnabled, + onCheckedChange = { messageNoticeEnabled = it } + ) + SettingItem( + title = "마케팅 알림", + description = "이벤트, 신규 기능 안내를 받습니다.", + enabled = marketingNoticeEnabled, + onCheckedChange = { marketingNoticeEnabled = it } + ) + } + + Column(verticalArrangement = Arrangement.spacedBy(SettingsMenuSpacing)) { + VersionInfoMenuItem(onClick = onVersionInfoClick) + LogoutMenuItem(onClick = { showSignOutDialog = true }) + } + } +} + +@Composable +private fun NotificationSettingsTopBar(onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().height(72.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.size(36.dp).clickable(onClick = onBackClick), + contentAlignment = Alignment.Center + ) { + Text("<", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + } + Text( + text = "알림 설정", + modifier = Modifier.padding(start = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 23.sp + ) + } +} + +@Composable +private fun SettingItem( + title: String, + description: String, + enabled: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Row( + modifier = Modifier.fillMaxWidth() + .padding(horizontal = SettingsItemInnerHorizontal, vertical = SettingsItemInnerVertical), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(SettingsDescriptionSpacing) + ) { + Text(title, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text( + text = description, + fontFamily = DotSans, + fontSize = 12.sp, + lineHeight = 16.sp, + color = ItdaSecondaryTextColor + ) + } + Switch( + checked = enabled, + onCheckedChange = onCheckedChange, + colors = SwitchDefaults.colors( + checkedThumbColor = ItdaWhite, + checkedTrackColor = ItdaHomeExploreButtonGray, + uncheckedThumbColor = ItdaWhite, + uncheckedTrackColor = Color(0xFFCCCCCC) + ) + ) + } + } +} + +@Composable +private fun VersionInfoMenuItem(onClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("버전 정보", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text(">", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp, color = ItdaSecondaryTextColor) + } + } +} + +@Composable +private fun LogoutMenuItem(onClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("로그아웃", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun NotificationSettingsScreenPreview() { + ITDATheme { NotificationSettingsScreen(onBackClick = {}, onVersionInfoClick = {}, onSignOutClick = {}) } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoRoute.kt new file mode 100644 index 0000000..2979de5 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoRoute.kt @@ -0,0 +1,11 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +@Composable +fun PersonalInfoRoute( + onBackClick: () -> Unit +) { + PersonalInfoScreen(onBackClick = onBackClick) +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoScreen.kt new file mode 100644 index 0000000..1188754 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/PersonalInfoScreen.kt @@ -0,0 +1,133 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +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.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val PersonalInfoHorizontalPadding = 24.dp +private val PersonalInfoSectionSpacing = 12.dp +private val PersonalInfoTopSpacing = 20.dp +private val PersonalInfoFieldSpacing = 6.dp + +@Composable +fun PersonalInfoScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + var name by rememberSaveable { mutableStateOf("메타몽") } + var department by rememberSaveable { mutableStateOf("SW개발과") } + var grade by rememberSaveable { mutableStateOf("1학년") } + var joinDate by rememberSaveable { mutableStateOf("2025년 1월") } + + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .padding(horizontal = PersonalInfoHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(PersonalInfoSectionSpacing) + ) { + PersonalInfoTopBar(onBackClick = onBackClick) + + Column(verticalArrangement = Arrangement.spacedBy(PersonalInfoSectionSpacing)) { + PersonalField("이름", name) { name = it } + PersonalField("학과", department) { department = it } + PersonalField("학년", grade) { grade = it } + PersonalField("가입일", joinDate) { joinDate = it } + } + + Surface( + modifier = Modifier.fillMaxWidth().height(48.dp).clickable(onClick = onBackClick), + shape = RoundedCornerShape(12.dp), + color = ItdaHomeExploreButtonGray + ) { + Box(contentAlignment = Alignment.Center) { + Text("저장", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp, color = ItdaWhite) + } + } + } +} + +@Composable +private fun PersonalField( + label: String, + value: String, + onValueChange: (String) -> Unit +) { + Column(verticalArrangement = Arrangement.spacedBy(PersonalInfoFieldSpacing)) { + Text(label, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 14.sp) + OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(10.dp), + textStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = DotSans), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = ItdaGuideGray, + unfocusedBorderColor = ItdaGuideGray, + unfocusedTextColor = MaterialTheme.colorScheme.onBackground, + focusedTextColor = MaterialTheme.colorScheme.onBackground + ) + ) + } +} + +@Composable +private fun PersonalInfoTopBar(onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().height(72.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.size(36.dp).clickable(onClick = onBackClick), + contentAlignment = Alignment.Center + ) { + Text("<", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + } + Text( + text = "개인 정보 입력", + modifier = Modifier.padding(start = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 23.sp + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PersonalInfoScreenPreview() { + ITDATheme { PersonalInfoScreen(onBackClick = {}) } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileRoute.kt new file mode 100644 index 0000000..9d1b12f --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileRoute.kt @@ -0,0 +1,25 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +// Connects profile screen callbacks with navigation from the graph. +@Composable +fun ProfileRoute( + onHomeTabClick: () -> Unit, + onNotificationSettingsClick: () -> Unit, + onProjectStatusClick: () -> Unit, + onSelfIntroductionClick: () -> Unit, + onPersonalInfoClick: () -> Unit +) { + MyProfileScreen( + onHomeTabClick = onHomeTabClick, + onExploreTabClick = {}, + onCreateProjectClick = {}, + onNotificationTabClick = {}, + onProfileTabClick = {}, + onNotificationSettingsClick = onNotificationSettingsClick, + onProjectStatusClick = onProjectStatusClick, + onSelfIntroductionClick = onSelfIntroductionClick, + onPersonalInfoClick = onPersonalInfoClick + ) +} diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileScreen.kt new file mode 100644 index 0000000..543dc02 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/ProfileScreen.kt @@ -0,0 +1,198 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +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.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +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.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.component.ItdaOutlinedBadge +import com.example.it_da.ui.screen.home.component.HomeBottomNavigationBar +import com.example.it_da.ui.screen.signup.component.SignUpTopBar +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val OtherProfileHorizontalPadding = 24.dp +private val OtherProfileOuterSpacing = 20.dp +private val OtherProfileTitleContentSpacing = 10.dp +private val OtherProfileChipSpacing = 6.dp +private val OtherProfileCardSpacing = 12.dp +private val OtherProfileHeaderSpacing = 20.dp +private val OtherProfileHeaderTextSpacing = 2.dp + +@Composable +fun ProfileScreen( + onHomeTabClick: () -> Unit, + onExploreTabClick: () -> Unit, + onCreateProjectClick: () -> Unit, + onNotificationTabClick: () -> Unit, + onProfileTabClick: () -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) { + Column(modifier = Modifier.fillMaxSize()) { + SignUpTopBar(title = "Project") + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = OtherProfileHorizontalPadding) + .padding(top = 22.dp, bottom = 84.dp), + verticalArrangement = Arrangement.spacedBy(OtherProfileOuterSpacing) + ) { + ProfileHeader() + Section("기술 스택") { + Row(horizontalArrangement = Arrangement.spacedBy(OtherProfileChipSpacing)) { + ItdaOutlinedBadge(text = "Back-end") + ItdaOutlinedBadge(text = "Java") + ItdaOutlinedBadge(text = "JavaScript") + ItdaOutlinedBadge(text = "Design") + } + } + Section("경력") { EmptyProfileBox(height = 86.dp) } + Section("자기 소개") { EmptyProfileBox(height = 90.dp) } + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + ProfileSectionTitle(text = "활동 요약") + ActivitySummaryRow() + Row(horizontalArrangement = Arrangement.spacedBy(OtherProfileCardSpacing)) { + ActionButton("프로젝트 전체 보기", modifier = Modifier.weight(1.35f)) + ActionButton("설정", modifier = Modifier.weight(1f)) + } + } + } + } + + HomeBottomNavigationBar( + onHomeClick = onHomeTabClick, + onExploreClick = onExploreTabClick, + onCreateProjectClick = onCreateProjectClick, + onNotificationClick = onNotificationTabClick, + onProfileClick = onProfileTabClick, + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + ) + } +} + +@Composable +private fun Section(title: String, content: @Composable () -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(OtherProfileTitleContentSpacing)) { + ProfileSectionTitle(text = title) + content() + } +} + +@Composable +private fun ProfileHeader() { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(OtherProfileHeaderSpacing) + ) { + Surface(modifier = Modifier.size(92.dp), shape = CircleShape, color = Color(0xFFE3E3E3)) { + Box(contentAlignment = Alignment.Center) { + Surface(modifier = Modifier.size(38.dp), shape = CircleShape, color = Color(0xFF8F8F8F)) {} + } + } + Column(verticalArrangement = Arrangement.spacedBy(OtherProfileHeaderTextSpacing)) { + Text("메타몽", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 35.sp, lineHeight = 40.sp) + Text("SW개발과 · 1학년", fontFamily = DotSans, fontSize = 23.sp, lineHeight = 26.sp) + Text("가입일 2025년 1월", fontFamily = DotSans, fontSize = 23.sp, lineHeight = 26.sp) + } + } +} + +@Composable +private fun ProfileSectionTitle(text: String) { + Text(text, fontFamily = DotSans, fontSize = 27.sp, lineHeight = 31.sp) +} + +@Composable +private fun EmptyProfileBox(height: Dp) { + Surface( + modifier = Modifier.fillMaxWidth().height(height), + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) {} +} + +@Composable +private fun ActivitySummaryRow() { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(OtherProfileCardSpacing)) { + ActivitySummaryCard("2", "참여 프로젝트", modifier = Modifier.weight(1f)) + ActivitySummaryCard("3", "지원 내역", modifier = Modifier.weight(1f)) + ActivitySummaryCard("1", "받은 제안", modifier = Modifier.weight(1f)) + } +} + +@Composable +private fun ActivitySummaryCard(value: String, label: String, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.height(72.dp), + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { + Text(value, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 17.sp) + Text(label, fontFamily = DotSans, fontSize = 11.sp, color = ItdaSecondaryTextColor) + } + } +} + +@Composable +private fun ActionButton(text: String, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.height(50.dp), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.2.dp, ItdaGuideGray) + ) { + Box(contentAlignment = Alignment.Center) { + Text(text, fontFamily = DotSans, fontSize = 14.sp, color = ItdaSecondaryTextColor) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ProfileScreenPreview() { + ITDATheme { + ProfileScreen( + onHomeTabClick = {}, + onExploreTabClick = {}, + onCreateProjectClick = {}, + onNotificationTabClick = {}, + onProfileTabClick = {} + ) + } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusRoute.kt new file mode 100644 index 0000000..1b5ad3c --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusRoute.kt @@ -0,0 +1,11 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +@Composable +fun ProjectStatusRoute( + onBackClick: () -> Unit +) { + ProjectStatusScreen(onBackClick = onBackClick) +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusScreen.kt new file mode 100644 index 0000000..bd94cbf --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/ProjectStatusScreen.kt @@ -0,0 +1,192 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +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.layout.statusBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val StatusHorizontalPadding = 24.dp +private val StatusSectionSpacing = 14.dp +private val StatusChipSpacing = 8.dp +private val StatusListSpacing = 10.dp +private val StatusCardInnerSpacing = 6.dp + +private enum class ProjectFilter(val label: String) { + Participating("참여 중"), + Applied("지원 내역"), + Finished("완료") +} + +private data class ProjectStatusUiModel( + val title: String, + val description: String, + val status: String +) + +@Composable +fun ProjectStatusScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + var selectedFilter by rememberSaveable { mutableStateOf(ProjectFilter.Participating) } + + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .padding(horizontal = StatusHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(StatusSectionSpacing) + ) { + ProjectStatusTopBar(onBackClick = onBackClick) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(StatusChipSpacing) + ) { + ProjectFilter.values().forEach { filter -> + FilterChip( + text = filter.label, + selected = selectedFilter == filter, + onClick = { selectedFilter = filter }, + modifier = Modifier.weight(1f) + ) + } + } + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(StatusListSpacing) + ) { + items(statusItems(selectedFilter)) { item -> + ProjectStatusCard(item = item) + } + } + } +} + +@Composable +private fun ProjectStatusTopBar(onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().height(72.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier.size(36.dp).clickable(onClick = onBackClick), contentAlignment = Alignment.Center) { + Text("<", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + } + Text( + text = "프로젝트 현황", + modifier = Modifier.padding(start = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 23.sp + ) + } +} + +@Composable +private fun FilterChip( + text: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier.height(38.dp).clickable(onClick = onClick), + shape = RoundedCornerShape(22.dp), + color = if (selected) ItdaHomeExploreButtonGray else ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = text, + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 13.sp, + color = if (selected) ItdaWhite else ItdaSecondaryTextColor + ) + } + } +} + +@Composable +private fun ProjectStatusCard(item: ProjectStatusUiModel) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Column( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 14.dp), + verticalArrangement = Arrangement.spacedBy(StatusCardInnerSpacing) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(item.title, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text(item.status, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 12.sp, color = ItdaSecondaryTextColor) + } + Text(item.description, fontFamily = DotSans, fontSize = 13.sp, color = ItdaSecondaryTextColor) + } + } +} + +private fun statusItems(filter: ProjectFilter): List { + return when (filter) { + ProjectFilter.Participating -> listOf( + ProjectStatusUiModel("IT-DA Android", "프로필/설정 화면 구현 진행 중", "진행중"), + ProjectStatusUiModel("캡스톤 매칭 서비스", "백엔드 API 연동 작업 진행", "진행중") + ) + ProjectFilter.Applied -> listOf( + ProjectStatusUiModel("AI 일정 도우미", "서류 심사 결과 대기 중", "대기"), + ProjectStatusUiModel("스터디 팀빌딩", "인터뷰 일정 조율 중", "검토중") + ) + ProjectFilter.Finished -> listOf( + ProjectStatusUiModel("학교 축제 웹", "프론트엔드 구현 담당 완료", "완료"), + ProjectStatusUiModel("동아리 홈페이지", "유지보수 배포까지 완료", "완료") + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun ProjectStatusScreenPreview() { + ITDATheme { ProjectStatusScreen(onBackClick = {}) } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionRoute.kt new file mode 100644 index 0000000..28961e4 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionRoute.kt @@ -0,0 +1,11 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +@Composable +fun SelfIntroductionRoute( + onBackClick: () -> Unit +) { + SelfIntroductionScreen(onBackClick = onBackClick) +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionScreen.kt new file mode 100644 index 0000000..3f6996e --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/SelfIntroductionScreen.kt @@ -0,0 +1,176 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +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.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaHomeExploreButtonGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val IntroHorizontalPadding = 24.dp +private val IntroSectionSpacing = 10.dp +private val IntroTopSpacing = 20.dp + +@Composable +fun SelfIntroductionScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + var introduction by rememberSaveable { + mutableStateOf("안녕하세요. 사용자 경험을 중요하게 생각하는 안드로이드 개발자입니다.") + } + var draftIntroduction by rememberSaveable { mutableStateOf(introduction) } + var showEditDialog by rememberSaveable { mutableStateOf(false) } + + if (showEditDialog) { + AlertDialog( + onDismissRequest = { showEditDialog = false }, + title = { Text("자기소개 수정", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) }, + text = { + OutlinedTextField( + value = draftIntroduction, + onValueChange = { draftIntroduction = it }, + modifier = Modifier.fillMaxWidth().height(170.dp), + placeholder = { Text("자기소개를 입력해 주세요.", fontFamily = DotSans, color = ItdaSecondaryTextColor) }, + shape = RoundedCornerShape(12.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = ItdaGuideGray, + unfocusedBorderColor = ItdaGuideGray + ) + ) + }, + confirmButton = { + Text( + text = "저장", + modifier = Modifier.clickable { + introduction = draftIntroduction + showEditDialog = false + }, + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + color = ItdaHomeExploreButtonGray + ) + }, + dismissButton = { + Text( + text = "취소", + modifier = Modifier.clickable { showEditDialog = false }, + fontFamily = DotSans, + fontSize = 14.sp, + color = ItdaSecondaryTextColor + ) + }, + containerColor = ItdaWhite + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .padding(horizontal = IntroHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(IntroSectionSpacing) + ) { + SelfIntroductionTopBar(onBackClick = onBackClick) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("자기소개", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 21.sp) + Surface( + modifier = Modifier.clickable { + draftIntroduction = introduction + showEditDialog = true + }, + shape = RoundedCornerShape(10.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray) + ) { + Text( + text = "수정하기", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 13.sp, + color = ItdaSecondaryTextColor + ) + } + } + + Surface( + modifier = Modifier.fillMaxWidth().height(220.dp), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray) + ) { + Text( + text = introduction, + modifier = Modifier.padding(14.dp), + fontFamily = DotSans, + fontSize = 15.sp, + lineHeight = 22.sp + ) + } + } +} + +@Composable +private fun SelfIntroductionTopBar(onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().height(72.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier.size(36.dp).clickable(onClick = onBackClick), contentAlignment = Alignment.Center) { + Text("<", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + } + Text( + text = "자기소개 작성", + modifier = Modifier.padding(start = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 23.sp + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun SelfIntroductionScreenPreview() { + ITDATheme { SelfIntroductionScreen(onBackClick = {}) } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoRoute.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoRoute.kt new file mode 100644 index 0000000..8ab9e43 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoRoute.kt @@ -0,0 +1,11 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.runtime.Composable + +@Composable +fun VersionInfoRoute( + onBackClick: () -> Unit +) { + VersionInfoScreen(onBackClick = onBackClick) +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoScreen.kt b/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoScreen.kt new file mode 100644 index 0000000..7f1b3f9 --- /dev/null +++ b/app/src/main/java/com/example/it_da/ui/screen/profile/VersionInfoScreen.kt @@ -0,0 +1,105 @@ +package com.example.it_da.ui.screen.profile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +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.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.it_da.BuildConfig +import com.example.it_da.ui.theme.DotSans +import com.example.it_da.ui.theme.ItdaGuideGray +import com.example.it_da.ui.theme.ItdaSecondaryTextColor +import com.example.it_da.ui.theme.ItdaWhite +import com.example.it_da.ui.theme.ITDATheme + +private val VersionHorizontalPadding = 24.dp +private val VersionSectionSpacing = 26.dp +private val VersionCardSpacing = 10.dp + +@Composable +fun VersionInfoScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .padding(horizontal = VersionHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(VersionSectionSpacing) + ) { + VersionTopBar(onBackClick = onBackClick) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + color = ItdaWhite, + border = BorderStroke(1.dp, ItdaGuideGray.copy(alpha = 0.45f)) + ) { + Column( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(VersionCardSpacing) + ) { + VersionRow(label = "현재 버전", value = BuildConfig.VERSION_NAME) + VersionRow(label = "버전 코드", value = BuildConfig.VERSION_CODE.toString()) + } + } + } +} + +@Composable +private fun VersionTopBar(onBackClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().height(72.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier.size(36.dp).clickable(onClick = onBackClick), contentAlignment = Alignment.Center) { + Text("<", fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 20.sp) + } + Text( + text = "버전 정보", + modifier = Modifier.padding(start = 8.dp), + fontFamily = DotSans, + fontWeight = FontWeight.Bold, + fontSize = 23.sp + ) + } +} + +@Composable +private fun VersionRow(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(label, fontFamily = DotSans, fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text(value, fontFamily = DotSans, fontSize = 14.sp, color = ItdaSecondaryTextColor) + } +} + +@Preview(showBackground = true) +@Composable +private fun VersionInfoScreenPreview() { + ITDATheme { VersionInfoScreen(onBackClick = {}) } +} + diff --git a/app/src/main/java/com/example/it_da/ui/screen/signup/component/SignUpPrimaryButton.kt b/app/src/main/java/com/example/it_da/ui/screen/signup/component/SignUpPrimaryButton.kt index 3bc517c..d2a06db 100644 --- a/app/src/main/java/com/example/it_da/ui/screen/signup/component/SignUpPrimaryButton.kt +++ b/app/src/main/java/com/example/it_da/ui/screen/signup/component/SignUpPrimaryButton.kt @@ -16,12 +16,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.example.it_da.ui.theme.ItdaLoginButtonDisabledBlack import com.example.it_da.ui.theme.ItdaButtonTextColor +import com.example.it_da.ui.theme.ItdaLoginButtonDisabledBlack private val SignUpPrimaryButtonTextWeight = FontWeight(600) -// Shows the sign-up primary action and lets Button enforce the enabled click rule. @Composable fun SignUpPrimaryButton( enabled: Boolean, @@ -60,3 +59,4 @@ fun SignUpPrimaryButton( } } } +