From 132783dbbb8de0682bdb5a078148f39164c8e2b3 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:01:02 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20AppHeader=20=EC=9D=BC=EB=B0=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/useai/core/ui/AppHeader.kt | 44 +++++++++++++------ .../kotlin/com/useai/feature/home/ui/Home.kt | 26 +++++------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/core/ui/src/main/kotlin/com/useai/core/ui/AppHeader.kt b/core/ui/src/main/kotlin/com/useai/core/ui/AppHeader.kt index e4a60820..ce9d66b6 100644 --- a/core/ui/src/main/kotlin/com/useai/core/ui/AppHeader.kt +++ b/core/ui/src/main/kotlin/com/useai/core/ui/AppHeader.kt @@ -3,6 +3,7 @@ package com.useai.core.ui import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -15,44 +16,47 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.useai.core.designsystem.R @Composable fun AppHeader( modifier: Modifier = Modifier, - onClickProfile: () -> Unit, + title: @Composable () -> Unit, + iconPainter: Painter, + iconDescription: String?, + iconSize: Dp, + onIconClick: () -> Unit, + paddingValues: PaddingValues = PaddingValues( + horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal), + ) ) { Row( modifier = modifier .fillMaxWidth() - .padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), + .padding(paddingValues), verticalAlignment = Alignment.CenterVertically, ) { - Image( - painter = painterResource(R.drawable.ic_symbol_word), - contentDescription = stringResource(R.string.content_description_app_logo), - modifier = Modifier - .height(28.dp) - .width(85.dp), - ) + title() Spacer(Modifier.weight(1f)) Box( modifier = Modifier.size(dimensionResource(R.dimen.app_header_user_profile_area_size)), contentAlignment = Alignment.Center, ) { Image( - painter = painterResource(R.drawable.ic_app_user), - contentDescription = stringResource(R.string.content_description_user_profile), + painter = iconPainter, + contentDescription = iconDescription, modifier = Modifier - .size(dimensionResource(R.dimen.app_header_user_profile_image_size)) + .size(iconSize) .clip(CircleShape) .clickable( - onClick = onClickProfile, + onClick = onIconClick, ), ) } @@ -63,6 +67,18 @@ fun AppHeader( @Composable private fun AppHeaderPreview() { AppHeader( - onClickProfile = {} + title = { + Image( + painter = painterResource(R.drawable.ic_symbol_word), + contentDescription = stringResource(R.string.content_description_app_logo), + modifier = Modifier + .height(28.dp) + .width(85.dp), + ) + }, + iconPainter = painterResource(R.drawable.ic_app_user), + iconDescription = stringResource(R.string.content_description_user_profile), + iconSize = dimensionResource(R.dimen.app_header_user_profile_image_size), + onIconClick = {} ) } diff --git a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt index ce9d2468..ad11943a 100644 --- a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt +++ b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt @@ -1,37 +1,25 @@ package com.useai.feature.home.ui import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.slack.circuit.codegen.annotations.CircuitInject import com.useai.core.designsystem.R -import com.useai.core.designsystem.component.button.LogitPrimaryButton import com.useai.core.designsystem.theme.LogitTheme import com.useai.core.model.project.ProjectListItem import com.useai.core.ui.AppHeader @@ -56,7 +44,19 @@ fun Home( ) { item { AppHeader( - onClickProfile = { + title = { + Image( + painter = painterResource(R.drawable.ic_symbol_word), + contentDescription = stringResource(R.string.content_description_app_logo), + modifier = Modifier + .height(28.dp) + .width(85.dp), + ) + }, + iconPainter = painterResource(R.drawable.ic_app_user), + iconDescription = stringResource(R.string.content_description_user_profile), + iconSize = dimensionResource(R.dimen.app_header_user_profile_image_size), + onIconClick = { state.eventSink(HomeScreen.Event.AccountClicked) } ) From 92a842e835b9f39d97c360d0bedd51fd57747bc4 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:02:22 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20AccountScreen=20data=20object?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/useai/logit/ScreenProviderImpl.kt | 1 + .../src/main/kotlin/com/useai/feature/account/AccountScreen.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt b/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt index 2bdd5d96..9e90efb6 100644 --- a/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt +++ b/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt @@ -15,6 +15,7 @@ class ScreenProviderImpl: ScreenProvider { override fun homeScreen(): Screen = HomeScreen override fun accountScreen(): Screen = AccountScreen() + override fun accountScreen(): Screen = AccountScreen override fun newProjectBasicInfoScreen(): Screen = NewProjectBasicInfoScreen diff --git a/feature/account/src/main/kotlin/com/useai/feature/account/AccountScreen.kt b/feature/account/src/main/kotlin/com/useai/feature/account/AccountScreen.kt index ed9175ef..4788802e 100644 --- a/feature/account/src/main/kotlin/com/useai/feature/account/AccountScreen.kt +++ b/feature/account/src/main/kotlin/com/useai/feature/account/AccountScreen.kt @@ -19,7 +19,7 @@ import dagger.hilt.android.components.ActivityRetainedComponent import kotlinx.parcelize.Parcelize @Parcelize -class AccountScreen : Screen { +data object AccountScreen : Screen { data class State( val userName: String, val reportNotificationEnabled: Boolean, From 22599957d6434d4e64f0969fd78e9f9d35c46a1f Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:03:54 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor:=20EmptyProjectList,=20ProjectList?= =?UTF-8?q?=20=EB=B3=84=EB=8F=84=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20ui=20=EB=AA=A8=EB=93=88=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useai/core/ui/project/EmptyProjectList.kt | 69 +++++++++ .../com/useai/core/ui/project/ProjectList.kt | 145 ++++++++++++++++++ .../kotlin/com/useai/feature/home/ui/Home.kt | 109 ++----------- 3 files changed, 224 insertions(+), 99 deletions(-) create mode 100644 core/ui/src/main/kotlin/com/useai/core/ui/project/EmptyProjectList.kt create mode 100644 core/ui/src/main/kotlin/com/useai/core/ui/project/ProjectList.kt diff --git a/core/ui/src/main/kotlin/com/useai/core/ui/project/EmptyProjectList.kt b/core/ui/src/main/kotlin/com/useai/core/ui/project/EmptyProjectList.kt new file mode 100644 index 00000000..f5df766a --- /dev/null +++ b/core/ui/src/main/kotlin/com/useai/core/ui/project/EmptyProjectList.kt @@ -0,0 +1,69 @@ +package com.useai.core.ui.project + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.useai.core.designsystem.R +import com.useai.core.designsystem.component.button.LogitPrimaryButton +import com.useai.core.designsystem.theme.LogitTheme + +@Composable +fun EmptyProjectList( + modifier: Modifier = Modifier, + onClickCreateProject: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 42.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(R.drawable.ic_empty_state), + contentDescription = null, + modifier = Modifier.size(80.dp), + ) + Spacer(Modifier.height(16.dp)) + Text( + text = stringResource(R.string.home_empty_project_phrase), + style = LogitTheme.typography.body6_2, + color = LogitTheme.colors.gray100, + ) + Spacer(Modifier.height(17.dp)) + LogitPrimaryButton( + text = stringResource(R.string.home_new_project), + onClick = { + onClickCreateProject() + }, + textStyle = LogitTheme.typography.body6_2, + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp), + ) + } +} + +@Preview +@Composable +private fun EmptyProjectListPreview() { + LogitTheme { + EmptyProjectList( + modifier = Modifier.background(LogitTheme.colors.white), + onClickCreateProject = {}, + ) + } +} diff --git a/core/ui/src/main/kotlin/com/useai/core/ui/project/ProjectList.kt b/core/ui/src/main/kotlin/com/useai/core/ui/project/ProjectList.kt new file mode 100644 index 00000000..e2dc741c --- /dev/null +++ b/core/ui/src/main/kotlin/com/useai/core/ui/project/ProjectList.kt @@ -0,0 +1,145 @@ +package com.useai.core.ui.project + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.useai.core.designsystem.R +import com.useai.core.designsystem.theme.LogitTheme +import com.useai.core.model.project.ProjectListItem +import java.time.LocalDate +import java.time.LocalDateTime + +@Composable +fun LazyListScope.ProjectList( + projects: List, + onClickProject: (String) -> Unit, +) { + itemsIndexed( + items = projects, + key = { _, project -> project.id } + ) { index, project -> + ProjectItem( + modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), + project = project, + onClick = { + onClickProject(project.id) + }, + ) + if (index < projects.lastIndex) { + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), + thickness = 1.dp, + color = LogitTheme.colors.gray70 + ) + } + } +} + +@Composable +private fun ProjectItem( + modifier: Modifier = Modifier, + project: ProjectListItem, + onClick: () -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable( + onClick = onClick, + ) + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(width = 3.dp, height = 24.dp) + .background(LogitTheme.colors.primary70) + ) + Spacer(Modifier.width(12.dp)) + Text( + text = stringResource( + R.string.home_project_list_item_title_format, + project.company, + project.jobPosition + ), + style = LogitTheme.typography.body6_2, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Spacer(Modifier.width(12.dp)) + Text( + text = project.dueDate.toString(), + style = LogitTheme.typography.body7_4, + ) + } +} + +@Preview +@Composable +private fun ProjectListPreview() { + LogitTheme { + LazyColumn( + modifier = Modifier.background(LogitTheme.colors.white) + ) { + item { + this@LazyColumn.ProjectList( + projects = listOf( + ProjectListItem( + id = "1", + company = "카카오페이", + jobPosition = "디자인 어시스턴트 어쩌구 저쩌구 어쩌구 저쩌구", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ProjectListItem( + id = "2", + company = "네이버", + jobPosition = "프론트엔드 개발", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ProjectListItem( + id = "3", + company = "토스", + jobPosition = "iOS 개발", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ), + onClickProject = {}, + ) + } + } + } +} diff --git a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt index ad11943a..b7a01c1f 100644 --- a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt +++ b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt @@ -27,6 +27,8 @@ import com.useai.core.ui.ExperienceBannerItem import com.useai.core.ui.ExperienceType import com.useai.core.ui.LogitExperienceBanner import com.useai.core.ui.LogitFormTitle +import com.useai.core.ui.project.EmptyProjectList +import com.useai.core.ui.project.ProjectList import com.useai.feature.home.HomeScreen import dagger.hilt.android.components.ActivityRetainedComponent import java.time.LocalDate @@ -85,116 +87,25 @@ fun Home( } } - if (state.projects.isEmpty()) { - item { + item { + if (state.projects.isEmpty()) { EmptyProjectList( - modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), onClickCreateProject = { state.eventSink(HomeScreen.Event.NewProjectClicked) - } + }, ) - } - } else { - itemsIndexed( - items = state.projects, - key = { _, project -> project.id } - ) { index, project -> - ProjectItem( - modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), - project = project, - onClick = { - state.eventSink(HomeScreen.Event.ProjectClicked(project.id)) + } else { + this@LazyColumn.ProjectList( + projects = state.projects, + onClickProject = { projectId -> + state.eventSink(HomeScreen.Event.ProjectClicked(projectId)) } ) - if (index < state.projects.lastIndex) { - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal)), - thickness = 1.dp, - color = LogitTheme.colors.gray70 - ) - } } } } } -@Composable -private fun EmptyProjectList( - modifier: Modifier = Modifier, - onClickCreateProject: () -> Unit, -) { - Column( - modifier = modifier - .fillMaxWidth() - .padding(vertical = 42.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - painter = painterResource(R.drawable.ic_empty_state), - contentDescription = null, - modifier = Modifier.size(80.dp), - ) - Spacer(Modifier.height(16.dp)) - Text( - text = stringResource(R.string.home_empty_project_phrase), - style = LogitTheme.typography.body6_2, - color = LogitTheme.colors.gray100, - ) - Spacer(Modifier.height(17.dp)) - LogitPrimaryButton( - text = stringResource(R.string.home_new_project), - onClick = { - onClickCreateProject() - }, - textStyle = LogitTheme.typography.body6_2, - shape = RoundedCornerShape(8.dp), - contentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp), - ) - } -} - -@Composable -private fun ProjectItem( - modifier: Modifier = Modifier, - project: ProjectListItem, - onClick: () -> Unit, -) { - Row( - modifier = modifier - .fillMaxWidth() - .clickable( - onClick = onClick, - ) - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Box( - modifier = Modifier - .size(width = 3.dp, height = 24.dp) - .background(LogitTheme.colors.primary70) - ) - Spacer(Modifier.width(12.dp)) - Text( - text = stringResource( - R.string.home_project_list_item_title_format, - project.company, - project.jobPosition - ), - style = LogitTheme.typography.body6_2, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Spacer(Modifier.width(12.dp)) - Text( - text = project.dueDate.toString(), - style = LogitTheme.typography.body7_4, - ) - } -} - @Preview @Composable private fun HomeWithEmptyProjectPreview() { From 5eea6dbda709dfb7dbc5789e4fa54eff5cf6c550 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:06:28 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20screen=5Fcommon=5Fpadding=5Fbot?= =?UTF-8?q?tom=20dimen=20res=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/designsystem/src/main/res/values/dimens.xml | 1 + .../home/src/main/kotlin/com/useai/feature/home/ui/Home.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/designsystem/src/main/res/values/dimens.xml b/core/designsystem/src/main/res/values/dimens.xml index dcd5c327..0b8adf66 100644 --- a/core/designsystem/src/main/res/values/dimens.xml +++ b/core/designsystem/src/main/res/values/dimens.xml @@ -2,6 +2,7 @@ 8dp 20dp + 20dp 44dp 35dp 12dp diff --git a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt index b7a01c1f..2777b7e4 100644 --- a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt +++ b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt @@ -42,7 +42,9 @@ fun Home( ) { LazyColumn( modifier = modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 20.dp) + contentPadding = PaddingValues( + bottom = dimensionResource(R.dimen.screen_common_padding_bottom), + ), ) { item { AppHeader( From 7f4e8b5f6cd7959c430ad38d9682b4569d6dad56 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:07:27 +0900 Subject: [PATCH 5/9] rename: home_project_list_title -> projects_title --- core/designsystem/src/main/res/values/strings.xml | 3 ++- feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index f084b4e0..02945e35 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -11,11 +11,12 @@ %s님의 경험 유형 관련경험 %d개 - 프로젝트 목록 자기소개서를 생성해보세요 자기소개서 작성 %s %s + 프로젝트 목록 + 자기소개서 업데이트 어떤 내용을 만들어볼까요? Q%s diff --git a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt index 2777b7e4..0deafdea 100644 --- a/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt +++ b/feature/home/src/main/kotlin/com/useai/feature/home/ui/Home.kt @@ -83,7 +83,7 @@ fun Home( LogitExperienceBanner(state.bannerItems) Spacer(Modifier.height(43.dp)) LogitFormTitle( - title = stringResource(R.string.home_project_list_title), + title = stringResource(R.string.projects_title), ) Spacer(Modifier.height(16.dp)) } From d137d328205c6153d0749ab2cdcdd93b42d7e4d5 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:08:54 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20feature.projects=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/projects/.gitignore | 1 + feature/projects/build.gradle.kts | 11 +++++++++ feature/projects/consumer-rules.pro | 0 feature/projects/proguard-rules.pro | 21 ++++++++++++++++ .../projects/ExampleInstrumentedTest.kt | 24 +++++++++++++++++++ feature/projects/src/main/AndroidManifest.xml | 4 ++++ .../useai/feature/projects/ExampleUnitTest.kt | 17 +++++++++++++ 7 files changed, 78 insertions(+) create mode 100644 feature/projects/.gitignore create mode 100644 feature/projects/build.gradle.kts create mode 100644 feature/projects/consumer-rules.pro create mode 100644 feature/projects/proguard-rules.pro create mode 100644 feature/projects/src/androidTest/java/com/useai/feature/projects/ExampleInstrumentedTest.kt create mode 100644 feature/projects/src/main/AndroidManifest.xml create mode 100644 feature/projects/src/test/java/com/useai/feature/projects/ExampleUnitTest.kt diff --git a/feature/projects/.gitignore b/feature/projects/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/projects/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/projects/build.gradle.kts b/feature/projects/build.gradle.kts new file mode 100644 index 00000000..225f6972 --- /dev/null +++ b/feature/projects/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + alias(libs.plugins.logit.android.library.common) + alias(libs.plugins.logit.compose.common) + alias(libs.plugins.logit.hilt) + alias(libs.plugins.logit.circuit) + alias(libs.plugins.logit.feature.common.dependencies) +} + +android { + namespace = "com.useai.feature.projects" +} diff --git a/feature/projects/consumer-rules.pro b/feature/projects/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/projects/proguard-rules.pro b/feature/projects/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/projects/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/projects/src/androidTest/java/com/useai/feature/projects/ExampleInstrumentedTest.kt b/feature/projects/src/androidTest/java/com/useai/feature/projects/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..b5018138 --- /dev/null +++ b/feature/projects/src/androidTest/java/com/useai/feature/projects/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.useai.feature.projects + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.useai.feature.projects.test", appContext.packageName) + } +} diff --git a/feature/projects/src/main/AndroidManifest.xml b/feature/projects/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/feature/projects/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/feature/projects/src/test/java/com/useai/feature/projects/ExampleUnitTest.kt b/feature/projects/src/test/java/com/useai/feature/projects/ExampleUnitTest.kt new file mode 100644 index 00000000..0e325c8b --- /dev/null +++ b/feature/projects/src/test/java/com/useai/feature/projects/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.useai.feature.projects + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} From 2baaa36b533033e55944966c828d106090be1ed5 Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:09:38 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20ProjectsScreen=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useai/feature/projects/ProjectsScreen.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 feature/projects/src/main/kotlin/com/useai/feature/projects/ProjectsScreen.kt diff --git a/feature/projects/src/main/kotlin/com/useai/feature/projects/ProjectsScreen.kt b/feature/projects/src/main/kotlin/com/useai/feature/projects/ProjectsScreen.kt new file mode 100644 index 00000000..ad5e2f3d --- /dev/null +++ b/feature/projects/src/main/kotlin/com/useai/feature/projects/ProjectsScreen.kt @@ -0,0 +1,78 @@ +package com.useai.feature.projects + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import com.slack.circuit.codegen.annotations.CircuitInject +import com.slack.circuit.runtime.CircuitUiEvent +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuit.runtime.screen.Screen +import com.useai.core.data.repository.ProjectRepository +import com.useai.core.model.project.ProjectListItem +import com.useai.core.navigation.LocalScreenProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.components.ActivityRetainedComponent +import kotlinx.parcelize.Parcelize + +@Parcelize +data object ProjectsScreen : Screen { + data class State( + val projects: List, + val eventSink: (Event) -> Unit = {}, + ) : CircuitUiState + + sealed interface Event : CircuitUiEvent { + data object NewProjectClicked : Event + data class ProjectClicked( + val projectId: String, + ) : Event + } +} + +class ProjectsPresenter @AssistedInject constructor( + @Assisted private val navigator: Navigator, + private val projectRepository: ProjectRepository, +) : Presenter { + @Composable + override fun present(): ProjectsScreen.State { + val projects by produceState(initialValue = emptyList()) { + projectRepository.getProjects() // TODO: 페이징 사용, 화면 진입 시마다 요청하지 않도록 개선 필요 + .onSuccess { value = it } + .onFailure { + Log.e(TAG, "getProjects failed: $it") + } + } + val screenProvider = LocalScreenProvider.current + + return ProjectsScreen.State( + projects = projects, + ) { event -> + when (event) { + ProjectsScreen.Event.NewProjectClicked -> { + navigator.goTo(screenProvider.newProjectBasicInfoScreen()) + } + + is ProjectsScreen.Event.ProjectClicked -> { + navigator.goTo(screenProvider.chatScreen(event.projectId)) + } + } + } + } + + @AssistedFactory + @CircuitInject(ProjectsScreen::class, ActivityRetainedComponent::class) + fun interface Factory { + fun create( + navigator: Navigator, + ): ProjectsPresenter + } + + companion object { + private val TAG = ProjectsPresenter::class.simpleName + } +} From d9060f554df600c3eb11620c6de47fb61b80f5ae Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:10:37 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/values/strings.xml | 2 + .../com/useai/feature/projects/ui/Projects.kt | 145 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 feature/projects/src/main/kotlin/com/useai/feature/projects/ui/Projects.kt diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 02945e35..311cb6ea 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ %s %s 프로젝트 목록 + %d개 자기소개서 업데이트 어떤 내용을 만들어볼까요? @@ -107,5 +108,6 @@ 로짓 로고 사용자 프로필 + 프로젝트 생성 diff --git a/feature/projects/src/main/kotlin/com/useai/feature/projects/ui/Projects.kt b/feature/projects/src/main/kotlin/com/useai/feature/projects/ui/Projects.kt new file mode 100644 index 00000000..c35ddc61 --- /dev/null +++ b/feature/projects/src/main/kotlin/com/useai/feature/projects/ui/Projects.kt @@ -0,0 +1,145 @@ +package com.useai.feature.projects.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.slack.circuit.codegen.annotations.CircuitInject +import com.useai.core.designsystem.R +import com.useai.core.designsystem.theme.LogitTheme +import com.useai.core.model.project.ProjectListItem +import com.useai.core.ui.AppHeader +import com.useai.core.ui.project.EmptyProjectList +import com.useai.core.ui.project.ProjectList +import com.useai.feature.projects.ProjectsScreen +import dagger.hilt.android.components.ActivityRetainedComponent +import java.time.LocalDate +import java.time.LocalDateTime + +@Composable +@CircuitInject(ProjectsScreen::class, ActivityRetainedComponent::class) +fun Projects( + modifier: Modifier = Modifier, + state: ProjectsScreen.State, +) { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues( + bottom = dimensionResource(R.dimen.screen_common_padding_bottom), + ) + ) { + item { + AppHeader( + title = { + Text( + text = stringResource(R.string.projects_title), + style = LogitTheme.typography.body1, + color = LogitTheme.colors.black, + ) + }, + iconPainter = painterResource(R.drawable.ic_tab_add), + iconDescription = stringResource(R.string.content_description_new_project), + iconSize = 16.dp, + onIconClick = { + state.eventSink(ProjectsScreen.Event.NewProjectClicked) + }, + paddingValues = PaddingValues( + start = dimensionResource(R.dimen.screen_common_padding_horizontal), + ), + ) + } + + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = dimensionResource(R.dimen.screen_common_padding_horizontal), + vertical = 6.dp + ), + ) { + Text( + text = stringResource(R.string.projects_count_format, state.projects.size), + style = LogitTheme.typography.body8_1, + color = LogitTheme.colors.gray200, + ) + } + } + + item { + if (state.projects.isEmpty()) { + EmptyProjectList( + onClickCreateProject = { + state.eventSink(ProjectsScreen.Event.NewProjectClicked) + } + ) + } else { + this@LazyColumn.ProjectList( + projects = state.projects, + onClickProject = { + state.eventSink(ProjectsScreen.Event.ProjectClicked(it)) + }, + ) + } + } + } +} + +@Preview +@Composable +private fun ProjectsPreview() { + LogitTheme { + Scaffold( + modifier = Modifier.fillMaxSize() + ) { paddingValues -> + Projects( + modifier = Modifier.padding(paddingValues), + state = ProjectsScreen.State( + projects = listOf( + ProjectListItem( + id = "1", + company = "카카오페이", + jobPosition = "디자인 어시스턴트 어쩌구 저쩌구 어쩌구 저쩌구", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ProjectListItem( + id = "2", + company = "네이버", + jobPosition = "프론트엔드 개발", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ProjectListItem( + id = "3", + company = "토스", + jobPosition = "iOS 개발", + dueDate = LocalDate.of(2026, 1, 7), + questionId = "", + totalQuestions = 0, + completedQuestions = 0, + updatedAt = LocalDateTime.now() + ), + ), + ), + ) + } + } +} From 503feae557e0655b9c4125eeb8f3869c783f734d Mon Sep 17 00:00:00 2001 From: Hyesung82 Date: Fri, 20 Feb 2026 00:11:13 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=9E=90=EC=86=8C=EC=84=9C=20?= =?UTF-8?q?=ED=83=AD=EC=97=90=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/src/main/java/com/useai/logit/ScreenProviderImpl.kt | 4 +++- .../main/java/com/useai/logit/navigation/TopLevelNavItem.kt | 6 +++--- app/src/main/java/com/useai/logit/ui/Root.kt | 4 ++-- .../main/java/com/useai/core/navigation/ScreenProvider.kt | 1 + settings.gradle.kts | 1 + 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9df02e41..97491f6f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { implementation(projects.feature.home) implementation(projects.feature.account) + implementation(projects.feature.projects) implementation(projects.feature.chat) implementation(projects.feature.newproject) implementation(projects.feature.experience) diff --git a/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt b/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt index 9e90efb6..907e9c9c 100644 --- a/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt +++ b/app/src/main/java/com/useai/logit/ScreenProviderImpl.kt @@ -10,13 +10,15 @@ import com.useai.feature.experience.ExperienceCreateScreen import com.useai.feature.experience.ExperienceDetailScreen import com.useai.feature.newproject.NewProjectBasicInfoScreen import com.useai.feature.newproject.NewProjectQuestionScreen +import com.useai.feature.projects.ProjectsScreen class ScreenProviderImpl: ScreenProvider { override fun homeScreen(): Screen = HomeScreen - override fun accountScreen(): Screen = AccountScreen() override fun accountScreen(): Screen = AccountScreen + override fun projectsScreen(): Screen = ProjectsScreen + override fun newProjectBasicInfoScreen(): Screen = NewProjectBasicInfoScreen override fun newProjectQuestionScreen( diff --git a/app/src/main/java/com/useai/logit/navigation/TopLevelNavItem.kt b/app/src/main/java/com/useai/logit/navigation/TopLevelNavItem.kt index fdb8a692..113d3832 100644 --- a/app/src/main/java/com/useai/logit/navigation/TopLevelNavItem.kt +++ b/app/src/main/java/com/useai/logit/navigation/TopLevelNavItem.kt @@ -5,10 +5,10 @@ import androidx.annotation.StringRes import com.slack.circuit.runtime.screen.Screen import com.useai.core.designsystem.R import com.useai.core.designsystem.icon.LogitIcons -import com.useai.feature.chat.ChatScreen import com.useai.feature.experience.ExperienceScreen import com.useai.feature.home.HomeScreen import com.useai.feature.newproject.NewProjectBasicInfoScreen +import com.useai.feature.projects.ProjectsScreen import com.useai.feature.report.ReportScreen data class TopLevelNavItem( @@ -21,7 +21,7 @@ data class TopLevelNavItem( fun fromScreen(screen: Screen): TopLevelNavItem { return when (screen) { is HomeScreen -> HOME - is ChatScreen -> COVER_LETTER + is ProjectsScreen -> PROJECTS is NewProjectBasicInfoScreen -> NEW_PROJECT is ExperienceScreen -> EXPERIENCE is ReportScreen -> REPORT @@ -38,7 +38,7 @@ val HOME = TopLevelNavItem( titleTextId = R.string.home_title, ) -val COVER_LETTER = TopLevelNavItem( +val PROJECTS = TopLevelNavItem( selectedIconId = LogitIcons.PaperSelected, unselectedIconId = LogitIcons.PaperDefault, iconTextId = R.string.cover_letter_title, diff --git a/app/src/main/java/com/useai/logit/ui/Root.kt b/app/src/main/java/com/useai/logit/ui/Root.kt index 546d4aaa..751b57b2 100644 --- a/app/src/main/java/com/useai/logit/ui/Root.kt +++ b/app/src/main/java/com/useai/logit/ui/Root.kt @@ -26,11 +26,11 @@ import com.useai.core.designsystem.component.LogitNavigationBarItem import com.useai.core.designsystem.component.snackbar.LocalLogitSnackbarHostState import com.useai.core.designsystem.component.snackbar.LogitSnackbarHost import com.useai.core.designsystem.theme.LogitTheme -import com.useai.feature.chat.ChatScreen import com.useai.feature.experience.ExperienceScreen import com.useai.feature.home.HomeScreen import com.useai.feature.newproject.NewProjectBasicInfoScreen import com.useai.feature.newproject.NewProjectQuestionScreen +import com.useai.feature.projects.ProjectsScreen import com.useai.feature.report.ReportScreen import com.useai.logit.RootScreen import com.useai.logit.navigation.TopLevelNavItem @@ -48,7 +48,7 @@ fun Root( val screens = remember { listOf( HomeScreen, - ChatScreen(""), + ProjectsScreen, NewProjectBasicInfoScreen, ExperienceScreen, ReportScreen, diff --git a/core/navigation/src/main/java/com/useai/core/navigation/ScreenProvider.kt b/core/navigation/src/main/java/com/useai/core/navigation/ScreenProvider.kt index f4c3c584..32c4f243 100644 --- a/core/navigation/src/main/java/com/useai/core/navigation/ScreenProvider.kt +++ b/core/navigation/src/main/java/com/useai/core/navigation/ScreenProvider.kt @@ -5,6 +5,7 @@ import com.slack.circuit.runtime.screen.Screen interface ScreenProvider { fun homeScreen(): Screen fun accountScreen(): Screen + fun projectsScreen(): Screen fun newProjectBasicInfoScreen(): Screen fun newProjectQuestionScreen( companyName: String, diff --git a/settings.gradle.kts b/settings.gradle.kts index 6b357ada..b3fbfda7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,3 +46,4 @@ include(":feature:newproject") include(":feature:experience") include(":feature:report") include(":feature:account") +include(":feature:projects")